From ac8b285afc0d7e7dff42dedb7022e5c3f64946ca Mon Sep 17 00:00:00 2001 From: Elwyn Malethan Date: Sat, 9 Nov 2019 14:04:19 +0000 Subject: [PATCH 001/282] Added translation using Weblate (Welsh) --- Riot/Assets/cy.lproj/Localizable.strings | 1 + 1 file changed, 1 insertion(+) create mode 100644 Riot/Assets/cy.lproj/Localizable.strings diff --git a/Riot/Assets/cy.lproj/Localizable.strings b/Riot/Assets/cy.lproj/Localizable.strings new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/Riot/Assets/cy.lproj/Localizable.strings @@ -0,0 +1 @@ + From 6375084d4ee1a86af0925cf72b345d6fe8718e5d Mon Sep 17 00:00:00 2001 From: Elwyn Malethan Date: Sat, 9 Nov 2019 14:05:08 +0000 Subject: [PATCH 002/282] Translated using Weblate (Welsh) Currently translated at 100.0% (28 of 28 strings) Translation: Riot iOS/Riot iOS (Push) Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios-push/cy/ --- Riot/Assets/cy.lproj/Localizable.strings | 57 +++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/Riot/Assets/cy.lproj/Localizable.strings b/Riot/Assets/cy.lproj/Localizable.strings index 8b13789179..4e00dae820 100644 --- a/Riot/Assets/cy.lproj/Localizable.strings +++ b/Riot/Assets/cy.lproj/Localizable.strings @@ -1 +1,56 @@ - +/* Message title for a specific person in a named room */ +"MSG_FROM_USER_IN_ROOM_TITLE" = "%@ yn %@"; +/* New message from a specific person, not referencing a room */ +"MSG_FROM_USER" = "Anfonwyd %@ neges"; +/* New message from a specific person in a named room */ +"MSG_FROM_USER_IN_ROOM" = "Postiodd %@ yn %@"; +/* New message from a specific person, not referencing a room. Content included. */ +"MSG_FROM_USER_WITH_CONTENT" = "%@: %@"; +/* New message from a specific person in a named room. Content included. */ +"MSG_FROM_USER_IN_ROOM_WITH_CONTENT" = "%@ yn %@: %@"; +/* New action message from a specific person, not referencing a room. */ +"ACTION_FROM_USER" = "* %@ %@"; +/* New action message from a specific person in a named room. */ +"ACTION_FROM_USER_IN_ROOM" = "%@: * %@ %@"; +/* New action message from a specific person, not referencing a room. */ +"IMAGE_FROM_USER" = "Anfonwyd %@ lun %@"; +/* New action message from a specific person in a named room. */ +"IMAGE_FROM_USER_IN_ROOM" = "Postiodd %@ lun %@ yn %@"; +/* A single unread message in a room */ +"SINGLE_UNREAD_IN_ROOM" = "Cawsoch neges yn %@"; +/* A single unread message */ +"SINGLE_UNREAD" = "Cawsoch neges"; +/* Sticker from a specific person, not referencing a room. */ +"STICKER_FROM_USER" = "Anfonodd %@ sticer"; +/* Multiple unread messages in a room */ +"UNREAD_IN_ROOM" = "%@ neges newydd yn %@"; +/* Multiple unread messages from a specific person, not referencing a room */ +"MSGS_FROM_USER" = "%@ neges newydd yn %@"; +/* Multiple unread messages from two people */ +"MSGS_FROM_TWO_USERS" = "%@ neges newydd gan %@ a %@"; +/* Multiple unread messages from three people */ +"MSGS_FROM_THREE_USERS" = "%@ neges newydd gan %@, %@ a %@"; +/* Multiple unread messages from two plus people (ie. for 4+ people: 'others' replaces the third person) */ +"MSGS_FROM_TWO_PLUS_USERS" = "%@ neges newydd gan %@, %@ ac eraill"; +/* Multiple messages in two rooms */ +"MSGS_IN_TWO_ROOMS" = "%@ neges newydd yn %@ a %@"; +/* Look, stuff's happened, alright? Just open the app. */ +"MSGS_IN_TWO_PLUS_ROOMS" = "%@ neges newydd yn %@, %@ ac eraill"; +/* A user has invited you to a chat */ +"USER_INVITE_TO_CHAT" = "Mae %@ wedi eich gwahodd chi i sgwrsio"; +/* A user has invited you to an (unamed) group chat */ +"USER_INVITE_TO_CHAT_GROUP_CHAT" = "Mae %@ wedi eich gwahodd chi i sgwrsio mewn grŵp"; +/* A user has invited you to a named room */ +"USER_INVITE_TO_NAMED_ROOM" = "Mae %@ wedi eich gwahodd chi i %@"; +/* Incoming one-to-one voice call */ +"VOICE_CALL_FROM_USER" = "Galwad gan %@"; +/* Incoming one-to-one video call */ +"VIDEO_CALL_FROM_USER" = "Galwad fideo gan %@"; +/* Incoming unnamed voice conference invite from a specific person */ +"VOICE_CONF_FROM_USER" = "Galwad grŵp gan %@"; +/* Incoming unnamed video conference invite from a specific person */ +"VIDEO_CONF_FROM_USER" = "Galwad fideo grŵp gan %@"; +/* Incoming named voice conference invite from a specific person */ +"VOICE_CONF_NAMED_FROM_USER" = "Galwad grŵp gan %@: '%@'"; +/* Incoming named video conference invite from a specific person */ +"VIDEO_CONF_NAMED_FROM_USER" = "Galwad fideo grŵp gan %@: '%@'"; From 915a8990fe2d157736b9310a422b3dc3a1d42df6 Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Tue, 12 Nov 2019 18:15:33 +0000 Subject: [PATCH 003/282] Translated using Weblate (Albanian) Currently translated at 99.4% (822 of 827 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/sq/ --- Riot/Assets/sq.lproj/Vector.strings | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Riot/Assets/sq.lproj/Vector.strings b/Riot/Assets/sq.lproj/Vector.strings index eea7f20a54..e706080d1f 100644 --- a/Riot/Assets/sq.lproj/Vector.strings +++ b/Riot/Assets/sq.lproj/Vector.strings @@ -891,3 +891,22 @@ "identity_server_settings_alert_disconnect_title" = "Shkëpute shërbyesin e identiteteve"; "identity_server_settings_alert_disconnect" = "Të bëhet shkëputja nga shërbyesi i identiteteve %@?"; "identity_server_settings_alert_disconnect_button" = "Shkëpute"; +"identity_server_settings_alert_disconnect_still_sharing_3pid" = "Ende ndani me të tjerët të dhëna tuajat personale në shërbyesin e identiteteve %@.\n\nKëshillojmë që të hiqni prej shërbyesit të identiteteve adresat tuaj email dhe numrat tuaj të telefonave përpara se të bëni shkëputjen."; +"identity_server_settings_alert_disconnect_still_sharing_3pid_button" = "Shkëputu, sido qoftë"; +"identity_server_settings_alert_error_terms_not_accepted" = "Duhet të pranoni termat e %@ që ta caktoni si shërbyes identitetesh."; +"identity_server_settings_alert_error_invalid_identity_server" = "%@ s’është shërbyes i vlershëm identitetesh."; +"call_no_stun_server_error_title" = "Thirrja dështoi për shkak shërbyesi të keqformësuar"; +"call_no_stun_server_error_message_1" = "Që thirrjet të funksionojnë pa probleme, ju lutemi, kërkojini përgjegjësit të shërbyesit tuaj Home %@ të formësojë një shërbyes TURN."; +"call_no_stun_server_error_message_2" = "Ndryshe, mund të provoni të përdorni shërbyesin publik te %@, por kjo s’do të jetë edhe aq e qëndrueshme, dhe adresa juaj IP do t’i bëhet e njohur atij shërbyesi. Këtë mund ta bëni edhe që nga Rregullimet"; +"call_no_stun_server_error_use_fallback_button" = "Provoni të përdorni %@"; +// Widget Picker +"widget_picker_title" = "Integrime"; +"service_terms_modal_decline_button" = "Hidhe poshtë"; +"service_terms_modal_description_for_identity_server_1" = "Gjeni të tjerë përmes telefoni ose email-i"; +"service_terms_modal_description_for_identity_server_2" = "Bëhuni i gjetshëm përmes telefoni ose email-i"; +// Service terms - Variant for identity server when displayed out of a context +"service_terms_modal_title_identity_server" = "Zbulim kontaktesh"; +"service_terms_modal_message_identity_server" = "Që të zbuloni kontakte, pranoni kushtet e shërbyesit të identiteteve (%@)."; +// Generic errors +"error_invite_3pid_with_no_identity_server" = "Që të ftoni me email, shtoni një shërbyes identitetesh, që nga rregullimet tuaja."; +"error_not_supported_on_mobile" = "Këtë s’mund ta bëni nga %@ për celular."; From 2e3970edae27094eee0fc143e26be880b9dcfa03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=ED=83=9C=EC=84=AD?= Date: Tue, 12 Nov 2019 12:30:57 +0000 Subject: [PATCH 004/282] Translated using Weblate (Korean) Currently translated at 100.0% (827 of 827 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/ko/ --- Riot/Assets/ko.lproj/Vector.strings | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Riot/Assets/ko.lproj/Vector.strings b/Riot/Assets/ko.lproj/Vector.strings index 88c5df6fd4..0be5746877 100644 --- a/Riot/Assets/ko.lproj/Vector.strings +++ b/Riot/Assets/ko.lproj/Vector.strings @@ -265,7 +265,7 @@ "room_event_action_reaction_show_all" = "모두 보이기"; "room_event_action_reaction_show_less" = "적게 보이기"; "room_event_action_reaction_history" = "리액션 기록"; -"room_warning_about_encryption" = "종단간 암호화는 베타 버전이고 신뢰하지 못할 수 있습니다.\n\n아직 데이터를 보호한다고 신뢰하지 마세요.\n\n기기가 방에 참가하기 전에 아직 기록을 해독할 수 없습니다.\n\n아직 암호화를 구현하지 않았기 때문에 암호화된 메시지는 클라이언트에 나타나지 않습니다."; +"room_warning_about_encryption" = "종단간 암호화는 베타 버전이고 신뢰하지 못할 수 있습니다.\n\n아직 데이터를 보호한다고 신뢰하지 마세요.\n\n기기가 방에 참가하기 전에 아직 기록을 복호화할 수 없습니다.\n\n아직 암호화를 구현하지 않았기 때문에 암호화된 메시지는 클라이언트에 나타나지 않습니다."; "room_event_failed_to_send" = "보내기에 실패함"; "room_action_camera" = "사진 또는 영상 찍기"; "room_action_send_photo_or_video" = "사진 또는 영상 보내기"; @@ -629,7 +629,7 @@ "deactivate_account_password_alert_message" = "계속하려면, 비밀번호를 입력해주세요"; // Re-request confirmation dialog "rerequest_keys_alert_title" = "요청을 보냈습니다"; -"rerequest_keys_alert_message" = "메시지를 해독해서 이 기기로 키를 보낼 수 있도록 Riot을 다른 기기에 설치해주세요."; +"rerequest_keys_alert_message" = "메시지를 복호화해서 이 기기로 키를 보낼 수 있도록 Riot을 다른 기기에 설치해주세요."; "key_backup_setup_title" = "키 백업"; "key_backup_setup_skip_alert_title" = "확신합니까?"; "key_backup_setup_skip_alert_message" = "로그아웃하거나 기기를 잃어버리면 보안 메시지를 잃게 됩니다."; @@ -665,9 +665,9 @@ "key_backup_setup_success_from_recovery_key_made_copy_action" = "사본을 만들었습니다"; "key_backup_recover_title" = "보안 메시지"; "key_backup_recover_invalid_passphrase_title" = "맞지 않는 복구 암호"; -"key_backup_recover_invalid_passphrase" = "이 암호로 백업을 해독할 수 없습니다: 올바른 복구 암호를 입력해서 확인해주세요."; +"key_backup_recover_invalid_passphrase" = "이 암호로 백업을 복호화할 수 없습니다: 올바른 복구 암호를 입력해서 확인해주세요."; "key_backup_recover_invalid_recovery_key_title" = "복구 키가 맞지 않음"; -"key_backup_recover_invalid_recovery_key" = "이 키로 백업을 해독할 수 없습니다: 올바른 복구 키를 입력해서 확인해주세요."; +"key_backup_recover_invalid_recovery_key" = "이 키로 백업을 복호화할 수 없습니다: 올바른 복구 키를 입력해서 확인해주세요."; "key_backup_recover_from_passphrase_info" = "복구 암호를 사용해 보안 메시지 기록을 푸세요"; "key_backup_recover_from_passphrase_passphrase_title" = "입력"; "key_backup_recover_from_passphrase_passphrase_placeholder" = "암호 입력"; From 0b2d4a22421525757171d3e0be8b0c743cb0586d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Thu, 21 Nov 2019 11:17:46 +0000 Subject: [PATCH 005/282] Translated using Weblate (French) Currently translated at 100.0% (831 of 831 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/fr/ --- Riot/Assets/fr.lproj/Vector.strings | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index 4f466ccdab..4ed0c9e983 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -916,3 +916,7 @@ "settings_add_3pid_password_message" = "Pour continuer, saisissez votre mot de passe"; "settings_add_3pid_invalid_password_message" = "Mot de passe non valide"; "error_not_supported_on_mobile" = "Vous ne pouvez pas faire cela depuis %@ mobile."; +"widget_menu_refresh" = "Actualiser"; +"widget_menu_open_outside" = "Ouvrir à l’extérieur"; +"widget_menu_revoke_permission" = "Révoquer l’accès pour moi"; +"widget_menu_remove" = "Supprimer pour tout le monde"; From 2e7e2df2639c355d6679a176100b47b07a45d331 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Wed, 20 Nov 2019 19:03:30 +0000 Subject: [PATCH 006/282] Translated using Weblate (Hungarian) Currently translated at 100.0% (831 of 831 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/hu/ --- Riot/Assets/hu.lproj/Vector.strings | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index 9c0ce9f952..f892b1bffe 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -921,3 +921,7 @@ "settings_add_3pid_password_message" = "A folytatáshoz add meg a jelszavadat"; "settings_add_3pid_invalid_password_message" = "Érvénytelen jelszó"; "error_not_supported_on_mobile" = "%@ mobilról ezt nem teheted meg."; +"widget_menu_refresh" = "Frissítés"; +"widget_menu_open_outside" = "Megnyitás kívül"; +"widget_menu_revoke_permission" = "Hozzáférés megvonása magamtól"; +"widget_menu_remove" = "Visszavonás mindenkitől"; From 05ae3ab6c6c2b2db1bb28e421c848af63961b6f9 Mon Sep 17 00:00:00 2001 From: random Date: Thu, 21 Nov 2019 09:32:02 +0000 Subject: [PATCH 007/282] Translated using Weblate (Italian) Currently translated at 100.0% (831 of 831 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/it/ --- Riot/Assets/it.lproj/Vector.strings | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Riot/Assets/it.lproj/Vector.strings b/Riot/Assets/it.lproj/Vector.strings index f1666242db..a2216bde0b 100644 --- a/Riot/Assets/it.lproj/Vector.strings +++ b/Riot/Assets/it.lproj/Vector.strings @@ -891,3 +891,7 @@ "settings_add_3pid_password_message" = "Per continuare, inserisci la tua password"; "settings_add_3pid_invalid_password_message" = "Password non valida"; "error_not_supported_on_mobile" = "Non puoi farlo da %@ mobile."; +"widget_menu_refresh" = "Ricarica"; +"widget_menu_open_outside" = "Apri all'esterno"; +"widget_menu_revoke_permission" = "Revoca l'accesso a me"; +"widget_menu_remove" = "Rimuovi per tutti"; From e9834993f6d4d62205a81993d642f86ef30ecabb Mon Sep 17 00:00:00 2001 From: Szimszon Date: Fri, 22 Nov 2019 10:57:23 +0000 Subject: [PATCH 008/282] Translated using Weblate (Hungarian) Currently translated at 100.0% (835 of 835 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/hu/ --- Riot/Assets/hu.lproj/Vector.strings | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index f892b1bffe..e158107b04 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -922,6 +922,10 @@ "settings_add_3pid_invalid_password_message" = "Érvénytelen jelszó"; "error_not_supported_on_mobile" = "%@ mobilról ezt nem teheted meg."; "widget_menu_refresh" = "Frissítés"; -"widget_menu_open_outside" = "Megnyitás kívül"; +"widget_menu_open_outside" = "Megnyitás böngészőben"; "widget_menu_revoke_permission" = "Hozzáférés megvonása magamtól"; "widget_menu_remove" = "Visszavonás mindenkitől"; +"settings_integrations" = "INTEGRÁCIÓK"; +"settings_integrations_allow_button" = "Integrációk kezelése"; +"settings_integrations_allow_description" = "Botok, hidak, kisalkalmazások és matrica csomagok kezeléséhez használj Integrációs Menedzsert (%@).\n\nIntegrációs Menedzser megkapja a konfigurációt, módosíthat kisalkalmazásokat, szobához meghívót küldhet és a hozzáférési szintet beállíthatja helyetted."; +"widget_integration_manager_disabled" = "Az integrációs menedzsert engedélyezned kell a beállításokban"; From 6f80c07b246c46f19fa31b5c75784de5cad3c796 Mon Sep 17 00:00:00 2001 From: Slavi Pantaleev Date: Fri, 22 Nov 2019 15:14:18 +0000 Subject: [PATCH 009/282] Translated using Weblate (Bulgarian) Currently translated at 100.0% (835 of 835 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/bg/ --- Riot/Assets/bg.lproj/Vector.strings | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Riot/Assets/bg.lproj/Vector.strings b/Riot/Assets/bg.lproj/Vector.strings index 41f81edf2b..9246d1c2c3 100644 --- a/Riot/Assets/bg.lproj/Vector.strings +++ b/Riot/Assets/bg.lproj/Vector.strings @@ -915,3 +915,11 @@ // Generic errors "error_invite_3pid_with_no_identity_server" = "Добавете сървър за самоличност в настройки за да каните по имейл."; "error_not_supported_on_mobile" = "Не може да правите това от %@ мобилен телефон."; +"settings_integrations" = "ИНТЕГРАЦИИ"; +"settings_integrations_allow_button" = "Управлявай интеграциите"; +"settings_integrations_allow_description" = "Използвайте мениджър на интеграции (%@) за да управлявате ботове, мостове към други мрежи, приспособления и стикери.\n\nМениджърите на интеграции получават данни за конфигурация, могат да модифицират приспособления, да пращат покани в стаи и да контролират нивата на достъп вместо вас."; +"widget_menu_refresh" = "Опресни"; +"widget_menu_open_outside" = "Отвори в браузър"; +"widget_menu_revoke_permission" = "Премахни достъпа за мен"; +"widget_menu_remove" = "Премахни за всички"; +"widget_integration_manager_disabled" = "Необходимо е да включите мениджър на интеграции от настройки"; From 0eaee29dc7ffce00e5affe573ca0f570d07872ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Sat, 23 Nov 2019 10:10:32 +0000 Subject: [PATCH 010/282] Translated using Weblate (French) Currently translated at 100.0% (835 of 835 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/fr/ --- Riot/Assets/fr.lproj/Vector.strings | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index 4ed0c9e983..31ebfc3fea 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -917,6 +917,10 @@ "settings_add_3pid_invalid_password_message" = "Mot de passe non valide"; "error_not_supported_on_mobile" = "Vous ne pouvez pas faire cela depuis %@ mobile."; "widget_menu_refresh" = "Actualiser"; -"widget_menu_open_outside" = "Ouvrir à l’extérieur"; +"widget_menu_open_outside" = "Ouvrir dans le navigateur"; "widget_menu_revoke_permission" = "Révoquer l’accès pour moi"; "widget_menu_remove" = "Supprimer pour tout le monde"; +"settings_integrations" = "INTÉGRATIONS"; +"settings_integrations_allow_button" = "Gérer les intégrations"; +"settings_integrations_allow_description" = "Utilisez un gestionnaire d’intégrations (%@) pour gérer les bots, les passerelles, les widgets et les packs de stickers.\n\nLes gestionnaires d’intégration reçoivent des données de configuration et peuvent modifier les widgets, envoyer des invitations de salon et définir des rangs à votre place."; +"widget_integration_manager_disabled" = "Vous devez activer le gestionnaire d’intégrations dans les paramètres"; From 5b59905307b6780e4f2667a74589519cce5483f7 Mon Sep 17 00:00:00 2001 From: random Date: Fri, 22 Nov 2019 13:57:09 +0000 Subject: [PATCH 011/282] Translated using Weblate (Italian) Currently translated at 100.0% (835 of 835 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/it/ --- Riot/Assets/it.lproj/Vector.strings | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Riot/Assets/it.lproj/Vector.strings b/Riot/Assets/it.lproj/Vector.strings index a2216bde0b..022e59737e 100644 --- a/Riot/Assets/it.lproj/Vector.strings +++ b/Riot/Assets/it.lproj/Vector.strings @@ -892,6 +892,10 @@ "settings_add_3pid_invalid_password_message" = "Password non valida"; "error_not_supported_on_mobile" = "Non puoi farlo da %@ mobile."; "widget_menu_refresh" = "Ricarica"; -"widget_menu_open_outside" = "Apri all'esterno"; +"widget_menu_open_outside" = "Apri nel browser"; "widget_menu_revoke_permission" = "Revoca l'accesso a me"; "widget_menu_remove" = "Rimuovi per tutti"; +"settings_integrations" = "INTEGRAZIONI"; +"settings_integrations_allow_button" = "Gestisci le integrazioni"; +"settings_integrations_allow_description" = "Usa un gestore di integrazioni (%@) per gestire bot, bridge, widget e pacchetti di adesivi.\n\nI gestori di integrazione ricevono dati di configurazione e possono modificare widget, inviare inviti alla stanza, assegnare permessi a tuo nome."; +"widget_integration_manager_disabled" = "Devi attivare il gestore di integrazioni nelle impostazioni"; From 73677b38f2aa6958a9bebfaf4b6922281504d1a7 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sat, 23 Nov 2019 18:11:38 +0000 Subject: [PATCH 012/282] Translated using Weblate (Italian) Currently translated at 100.0% (835 of 835 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/it/ --- Riot/Assets/it.lproj/Vector.strings | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Riot/Assets/it.lproj/Vector.strings b/Riot/Assets/it.lproj/Vector.strings index 022e59737e..3fdfb3df48 100644 --- a/Riot/Assets/it.lproj/Vector.strings +++ b/Riot/Assets/it.lproj/Vector.strings @@ -7,7 +7,7 @@ "title_groups" = "Comunità"; "warning" = "Attenzione"; "next" = "Prossimo"; -"leave" = "Lascia"; +"leave" = "Esci"; "remove" = "Rimuovi"; "invite" = "Invita"; "cancel" = "Annulla"; @@ -61,7 +61,7 @@ // String for App Store "store_short_description" = "Conversazioni sicure e decentralizzate"; // Actions -"view" = "Vedi"; +"view" = "Visualizza"; "back" = "Indietro"; "continue" = "Continua"; "create" = "Crea"; @@ -391,7 +391,7 @@ "settings_key_backup_delete_confirmation_prompt_title" = "Elimina backup"; "settings_key_backup_delete_confirmation_prompt_msg" = "Sei sicuro? Se non hai eseguito un Backup delle Chiavi perderai i tuoi messaggi crittografati."; // Room Details -"room_details_title" = "Dettagli stanza"; +"room_details_title" = "Dettagli canale"; "room_details_people" = "Membri"; "room_details_files" = "File"; "room_details_settings" = "Impostazioni"; From 196444b4e40bf28b96eadbb5443c1d380246a68c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=ED=83=9C=EC=84=AD?= Date: Sat, 23 Nov 2019 15:37:42 +0000 Subject: [PATCH 013/282] Translated using Weblate (Korean) Currently translated at 100.0% (835 of 835 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/ko/ --- Riot/Assets/ko.lproj/Vector.strings | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Riot/Assets/ko.lproj/Vector.strings b/Riot/Assets/ko.lproj/Vector.strings index 0be5746877..90d153d913 100644 --- a/Riot/Assets/ko.lproj/Vector.strings +++ b/Riot/Assets/ko.lproj/Vector.strings @@ -889,3 +889,11 @@ "settings_add_3pid_password_message" = "계속하려면 비밀번호를 입력해주세요"; "settings_add_3pid_invalid_password_message" = "잘못된 비밀번호"; "error_not_supported_on_mobile" = "%@ 모바일에서 할 수 없습니다."; +"settings_integrations" = "통합"; +"settings_integrations_allow_button" = "통합 관리"; +"settings_integrations_allow_description" = "통합 관리자 (%@)를 사용해 봇, 브릿지, 위젯과 스티커 팩을 관리하세요.\n\n통합 관리자는 설정 데이터를 받고 위젯을 수정하거나, 방 초대를 보내고 권한 등급을 설정할 수 있습니다."; +"widget_menu_refresh" = "새로고침"; +"widget_menu_open_outside" = "브라우저에서 열기"; +"widget_menu_revoke_permission" = "액세스 취소"; +"widget_menu_remove" = "모두를 위해 제거"; +"widget_integration_manager_disabled" = "설정에서 통합 관리자를 켜야 합니다"; From 2fa5caad20b3f217c3e57334dca4bdd7508cdef1 Mon Sep 17 00:00:00 2001 From: dccs Date: Mon, 25 Nov 2019 08:01:39 +0000 Subject: [PATCH 014/282] Translated using Weblate (German) Currently translated at 99.5% (831 of 835 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/de/ --- Riot/Assets/de.lproj/Vector.strings | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Riot/Assets/de.lproj/Vector.strings b/Riot/Assets/de.lproj/Vector.strings index 5c6ca7b393..3f9f78ddfe 100644 --- a/Riot/Assets/de.lproj/Vector.strings +++ b/Riot/Assets/de.lproj/Vector.strings @@ -897,3 +897,10 @@ "settings_add_3pid_invalid_password_message" = "Ungültiges Passwort"; "identity_server_settings_disconnect_info" = "Wenn Sie die Verbindung zu Ihrem Identitätsserver trennen, werden Sie von anderen Benutzern nicht erkannt und können andere per E-Mail oder Telefon einladen."; "error_not_supported_on_mobile" = "Dies ist in %@ mobile nicht möglich."; +"settings_integrations" = "INTEGRATIONEN"; +"settings_integrations_allow_button" = "Integrationen verwalten"; +"widget_menu_refresh" = "Aktualisierung"; +"widget_menu_open_outside" = "Im Browser öffnen"; +"widget_menu_revoke_permission" = "Zugriff für mich widerrufen"; +"widget_menu_remove" = "Für alle entfernen"; +"widget_integration_manager_disabled" = "Sie müssen den Integration Manager in den Einstellungen aktivieren"; From b4d29e0b3ba9b452255a26aa6898d90e71489262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Thu, 28 Nov 2019 14:14:04 +0000 Subject: [PATCH 015/282] Translated using Weblate (French) Currently translated at 100.0% (838 of 838 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/fr/ --- Riot/Assets/fr.lproj/Vector.strings | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index 31ebfc3fea..e7f4dfe46b 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -924,3 +924,6 @@ "settings_integrations_allow_button" = "Gérer les intégrations"; "settings_integrations_allow_description" = "Utilisez un gestionnaire d’intégrations (%@) pour gérer les bots, les passerelles, les widgets et les packs de stickers.\n\nLes gestionnaires d’intégration reçoivent des données de configuration et peuvent modifier les widgets, envoyer des invitations de salon et définir des rangs à votre place."; "widget_integration_manager_disabled" = "Vous devez activer le gestionnaire d’intégrations dans les paramètres"; +"widget_room_permission_title" = "Charger le widget"; +"widget_room_permission_creator_info_title" = "Ce widget a été ajouté par :"; +"widget_room_permission_information" = "Son utilisation peut utiliser des cookies et partager des données avec %@ :\n\n• Votre nom affiché\n• L’URL de votre avatar\n• Votre identifiant d’utilisateur\n• Votre thème\n• L’identifiant du salon\n• L’identifiant du widget"; From 27444a1733c3b1b9fc83ddbd18ac494a06f2a2c6 Mon Sep 17 00:00:00 2001 From: Osoitz Date: Sat, 30 Nov 2019 10:32:43 +0000 Subject: [PATCH 016/282] Translated using Weblate (Basque) Currently translated at 100.0% (845 of 845 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/eu/ --- Riot/Assets/eu.lproj/Vector.strings | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Riot/Assets/eu.lproj/Vector.strings b/Riot/Assets/eu.lproj/Vector.strings index 435cd11357..0328db0124 100644 --- a/Riot/Assets/eu.lproj/Vector.strings +++ b/Riot/Assets/eu.lproj/Vector.strings @@ -904,3 +904,22 @@ "settings_add_3pid_password_message" = "Jarraitzeko sartu zure pasahitza"; "settings_add_3pid_invalid_password_message" = "Pasahitz baliogabea"; "error_not_supported_on_mobile" = "Ezin duzu hau %@ mugikorretik egin."; +"settings_integrations" = "INTEGRAZIOAK"; +"settings_integrations_allow_button" = "Kudeatu integrazioak"; +"settings_integrations_allow_description" = "Erabili integrazio kudeatzaileren bat botak, zubiak, trepetak eta eranskailu multzoak kudeatzeko.\n\nIntegrazio kudeatzaileek konfigurazio datuak jasotzen dituzte, eta trepetak aldatu ditzakete, gelarako gonbidapenak bidali, eta botere mailak zure izenean ezarri."; +"widget_menu_refresh" = "Freskatu"; +"widget_menu_open_outside" = "Ireki nabigatzailean"; +"widget_menu_revoke_permission" = "Indargabetu sarbidea niretzat"; +"widget_menu_remove" = "Kendu denentzat"; +"widget_integration_manager_disabled" = "Integrazio kudeatzaileak gaitu behar dituzu ezarpenetan"; +// Room widget permissions +"room_widget_permission_title" = "Kargatu trepeta"; +"room_widget_permission_creator_info_title" = "Trepeta hau honek gehitu du:"; +"room_widget_permission_webview_information_title" = "Hau erabiltzean cookieak ezarri litezke eta %@ zerbitzariarekin datuak partekatu:\n"; +"room_widget_permission_information_title" = "Hau erabiltzean %@ zerbitzariarekin datuak partekatu litezke:\n"; +"room_widget_permission_display_name_permission" = "Zure pantaila-izena"; +"room_widget_permission_avatar_url_permission" = "Zure abatarraren URL-a"; +"room_widget_permission_user_id_permission" = "Zure erabiltzaile ID-a"; +"room_widget_permission_theme_permission" = "Zure gaia"; +"room_widget_permission_widget_id_permission" = "Trepetaren ID-a"; +"room_widget_permission_room_id_permission" = "Gelaren ID-a"; From 4f89de1802cd09574ec92102be91d7075dc20af2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Fri, 29 Nov 2019 16:07:57 +0000 Subject: [PATCH 017/282] Translated using Weblate (French) Currently translated at 100.0% (845 of 845 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/fr/ --- Riot/Assets/fr.lproj/Vector.strings | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index e7f4dfe46b..ce173345ae 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -927,3 +927,14 @@ "widget_room_permission_title" = "Charger le widget"; "widget_room_permission_creator_info_title" = "Ce widget a été ajouté par :"; "widget_room_permission_information" = "Son utilisation peut utiliser des cookies et partager des données avec %@ :\n\n• Votre nom affiché\n• L’URL de votre avatar\n• Votre identifiant d’utilisateur\n• Votre thème\n• L’identifiant du salon\n• L’identifiant du widget"; +// Room widget permissions +"room_widget_permission_title" = "Charger un widget"; +"room_widget_permission_creator_info_title" = "Ce widget a été ajouté par :"; +"room_widget_permission_webview_information_title" = "Son utilisation peut entraîner l’utilisation de cookies et le partage de données avec %@ :\n"; +"room_widget_permission_information_title" = "Son utilisation peut entraîner le partage de données avec %@ :\n"; +"room_widget_permission_display_name_permission" = "Votre nom affiché"; +"room_widget_permission_avatar_url_permission" = "L’URL de votre avatar"; +"room_widget_permission_user_id_permission" = "Votre identifiant d’utilisateur"; +"room_widget_permission_theme_permission" = "Votre thème"; +"room_widget_permission_widget_id_permission" = "L’identifiant du widget"; +"room_widget_permission_room_id_permission" = "L’identifiant du salon"; From 16ea697dd1b9c78a22b76be01939b42e40fedf9d Mon Sep 17 00:00:00 2001 From: Szimszon Date: Fri, 29 Nov 2019 18:52:33 +0000 Subject: [PATCH 018/282] Translated using Weblate (Hungarian) Currently translated at 100.0% (845 of 845 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/hu/ --- Riot/Assets/hu.lproj/Vector.strings | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index e158107b04..4035eae136 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -929,3 +929,14 @@ "settings_integrations_allow_button" = "Integrációk kezelése"; "settings_integrations_allow_description" = "Botok, hidak, kisalkalmazások és matrica csomagok kezeléséhez használj Integrációs Menedzsert (%@).\n\nIntegrációs Menedzser megkapja a konfigurációt, módosíthat kisalkalmazásokat, szobához meghívót küldhet és a hozzáférési szintet beállíthatja helyetted."; "widget_integration_manager_disabled" = "Az integrációs menedzsert engedélyezned kell a beállításokban"; +// Room widget permissions +"room_widget_permission_title" = "Kisalkalmazás betöltése"; +"room_widget_permission_creator_info_title" = "Ezt a kisalkalmazást hozzáadta:"; +"room_widget_permission_webview_information_title" = "A használatához lehet, hogy sütiket kell használni és adat lesz megosztva ezzel: %@:\n"; +"room_widget_permission_information_title" = "A használatához lehet, hogy adat lesz megosztva ezzel: %@:\n"; +"room_widget_permission_display_name_permission" = "Megjelenítési neved"; +"room_widget_permission_avatar_url_permission" = "Profilképed URL-je"; +"room_widget_permission_user_id_permission" = "Felhasználói azonosítód"; +"room_widget_permission_theme_permission" = "Témád"; +"room_widget_permission_widget_id_permission" = "Kisalkalmazás azon."; +"room_widget_permission_room_id_permission" = "Szoba azonosító"; From e11556d024ecb819f73349bad286eb0470c2929a Mon Sep 17 00:00:00 2001 From: random Date: Mon, 2 Dec 2019 09:55:30 +0000 Subject: [PATCH 019/282] Translated using Weblate (Italian) Currently translated at 100.0% (845 of 845 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/it/ --- Riot/Assets/it.lproj/Vector.strings | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Riot/Assets/it.lproj/Vector.strings b/Riot/Assets/it.lproj/Vector.strings index 3fdfb3df48..0a553b952f 100644 --- a/Riot/Assets/it.lproj/Vector.strings +++ b/Riot/Assets/it.lproj/Vector.strings @@ -899,3 +899,14 @@ "settings_integrations_allow_button" = "Gestisci le integrazioni"; "settings_integrations_allow_description" = "Usa un gestore di integrazioni (%@) per gestire bot, bridge, widget e pacchetti di adesivi.\n\nI gestori di integrazione ricevono dati di configurazione e possono modificare widget, inviare inviti alla stanza, assegnare permessi a tuo nome."; "widget_integration_manager_disabled" = "Devi attivare il gestore di integrazioni nelle impostazioni"; +// Room widget permissions +"room_widget_permission_title" = "Carica widget"; +"room_widget_permission_creator_info_title" = "Questo widget è stato aggiunto da:"; +"room_widget_permission_webview_information_title" = "Usarlo potrebbe impostare cookie e condividere dati con %@:\n"; +"room_widget_permission_information_title" = "Usarlo potrebbe condividere dati con %@:\n"; +"room_widget_permission_display_name_permission" = "Il tuo nome visualizzato"; +"room_widget_permission_avatar_url_permission" = "Il tuo URL dell'avatar"; +"room_widget_permission_user_id_permission" = "Il tuo ID utente"; +"room_widget_permission_theme_permission" = "Il tuo tema"; +"room_widget_permission_widget_id_permission" = "ID widget"; +"room_widget_permission_room_id_permission" = "ID stanza"; From 63aa088b357ec25270e9406588bccd3af5ae3010 Mon Sep 17 00:00:00 2001 From: Miren Date: Tue, 3 Dec 2019 08:20:30 +0000 Subject: [PATCH 020/282] Translated using Weblate (Basque) Currently translated at 100.0% (846 of 846 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/eu/ --- Riot/Assets/eu.lproj/Vector.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Assets/eu.lproj/Vector.strings b/Riot/Assets/eu.lproj/Vector.strings index 0328db0124..cea12ce87e 100644 --- a/Riot/Assets/eu.lproj/Vector.strings +++ b/Riot/Assets/eu.lproj/Vector.strings @@ -923,3 +923,4 @@ "room_widget_permission_theme_permission" = "Zure gaia"; "room_widget_permission_widget_id_permission" = "Trepetaren ID-a"; "room_widget_permission_room_id_permission" = "Gelaren ID-a"; +"widget_picker_manage_integrations" = "Integrazioak kudeatu..."; From aed4b2e994898eec091eaca9738d2001f25cdfb2 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Mon, 2 Dec 2019 14:22:55 +0000 Subject: [PATCH 021/282] Translated using Weblate (Hungarian) Currently translated at 100.0% (846 of 846 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/hu/ --- Riot/Assets/hu.lproj/Vector.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index 4035eae136..8560ac8c6e 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -940,3 +940,4 @@ "room_widget_permission_theme_permission" = "Témád"; "room_widget_permission_widget_id_permission" = "Kisalkalmazás azon."; "room_widget_permission_room_id_permission" = "Szoba azonosító"; +"widget_picker_manage_integrations" = "Integrációk kezelése…"; From 6041364a78a5b8dafdffa45b7799cbd3e6955a82 Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Tue, 3 Dec 2019 10:39:56 +0000 Subject: [PATCH 022/282] Translated using Weblate (Albanian) Currently translated at 99.4% (843 of 848 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/sq/ --- Riot/Assets/sq.lproj/Vector.strings | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Riot/Assets/sq.lproj/Vector.strings b/Riot/Assets/sq.lproj/Vector.strings index e706080d1f..9918d92a09 100644 --- a/Riot/Assets/sq.lproj/Vector.strings +++ b/Riot/Assets/sq.lproj/Vector.strings @@ -910,3 +910,26 @@ // Generic errors "error_invite_3pid_with_no_identity_server" = "Që të ftoni me email, shtoni një shërbyes identitetesh, që nga rregullimet tuaja."; "error_not_supported_on_mobile" = "Këtë s’mund ta bëni nga %@ për celular."; +// Accessibility +"accessibility_checkbox_label" = "kutizë"; +"settings_integrations" = "INTEGRIME"; +"settings_integrations_allow_button" = "Administroni integrime"; +"settings_integrations_allow_description" = "Përdorni një Përgjegjës Integrimesh (%@) që të administroni robotë, ura, widget-e dhe paketa ngjitësish.\n\nPërgjegjësit e Integrimeve marrin të dhëna formësimi dhe mund të ndryshojnë widget-e, të dërgojnë ftesa për në dhoma dhe të caktojnë shkallë pushteti në emrin tuaj."; +"widget_menu_refresh" = "Rifreskoje"; +"widget_menu_open_outside" = "Hape në shfletues"; +"widget_menu_revoke_permission" = "Shfuqizo hyrje për mua"; +"widget_menu_remove" = "Hiqe për këdo"; +"widget_integration_manager_disabled" = "Lypset të aktivizoni Përgjegjës Integrimesh te rregullimet"; +"widget_picker_manage_integrations" = "Administroni integrime…"; +// Room widget permissions +"room_widget_permission_title" = "Ngarko Widget"; +"room_widget_permission_creator_info_title" = "Ky widget qe shtuar nga:"; +"room_widget_permission_webview_information_title" = "Përdorimi i tij mund të sjellë depozitim cookies dhe ndarje të dhënash me %@:\n"; +"room_widget_permission_information_title" = "Përdorimi i tij mund të sjellë ndarje të dhënash me %@:\n"; +"room_widget_permission_display_name_permission" = "Emri juaj në ekran"; +"room_widget_permission_avatar_url_permission" = "URL-ja e avatarit tuaj"; +"room_widget_permission_user_id_permission" = "ID-ja juaj e përdoruesit"; +"room_widget_permission_theme_permission" = "Tema juaj"; +"room_widget_permission_widget_id_permission" = "ID Widget-i"; +"room_widget_permission_room_id_permission" = "ID Dhome"; +"service_terms_modal_policy_checkbox_accessibility_hint" = "I vini shenjë që të pranohet %@"; From 4a5a6316cf07cbbb80e7c098624d5e116a7a7275 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Tue, 3 Dec 2019 22:22:25 +0000 Subject: [PATCH 023/282] Translated using Weblate (Hungarian) Currently translated at 100.0% (848 of 848 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/hu/ --- Riot/Assets/hu.lproj/Vector.strings | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index 8560ac8c6e..4d59373787 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -941,3 +941,6 @@ "room_widget_permission_widget_id_permission" = "Kisalkalmazás azon."; "room_widget_permission_room_id_permission" = "Szoba azonosító"; "widget_picker_manage_integrations" = "Integrációk kezelése…"; +// Accessibility +"accessibility_checkbox_label" = "jelölőnégyzet"; +"service_terms_modal_policy_checkbox_accessibility_hint" = "Az engedélyezéshez jelöld be: %@"; From a1cced11c2ff60a30faa51942599052305c43f40 Mon Sep 17 00:00:00 2001 From: random Date: Tue, 3 Dec 2019 11:38:24 +0000 Subject: [PATCH 024/282] Translated using Weblate (Italian) Currently translated at 100.0% (848 of 848 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/it/ --- Riot/Assets/it.lproj/Vector.strings | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Riot/Assets/it.lproj/Vector.strings b/Riot/Assets/it.lproj/Vector.strings index 0a553b952f..dd8d3669cb 100644 --- a/Riot/Assets/it.lproj/Vector.strings +++ b/Riot/Assets/it.lproj/Vector.strings @@ -910,3 +910,7 @@ "room_widget_permission_theme_permission" = "Il tuo tema"; "room_widget_permission_widget_id_permission" = "ID widget"; "room_widget_permission_room_id_permission" = "ID stanza"; +// Accessibility +"accessibility_checkbox_label" = "checkbox"; +"widget_picker_manage_integrations" = "Gestisci integrazioni..."; +"service_terms_modal_policy_checkbox_accessibility_hint" = "Seleziona per accettare %@"; From 4f31bed1b9b9001f152c70988c5e43f7b94ad3c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Fri, 6 Dec 2019 09:27:23 +0000 Subject: [PATCH 025/282] Translated using Weblate (French) Currently translated at 100.0% (848 of 848 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/fr/ --- Riot/Assets/fr.lproj/Vector.strings | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index ce173345ae..906dacb634 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -938,3 +938,7 @@ "room_widget_permission_theme_permission" = "Votre thème"; "room_widget_permission_widget_id_permission" = "L’identifiant du widget"; "room_widget_permission_room_id_permission" = "L’identifiant du salon"; +// Accessibility +"accessibility_checkbox_label" = "case à cocher"; +"widget_picker_manage_integrations" = "Gérer les intégrations…"; +"service_terms_modal_policy_checkbox_accessibility_hint" = "Cochez pour accepter %@"; From 5110eb68f62fa20c57e9c48c2e2d055835de084a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Sat, 7 Dec 2019 08:56:07 +0000 Subject: [PATCH 026/282] Translated using Weblate (French) Currently translated at 100.0% (850 of 850 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/fr/ --- Riot/Assets/fr.lproj/Vector.strings | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index 906dacb634..84603356f5 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -942,3 +942,5 @@ "accessibility_checkbox_label" = "case à cocher"; "widget_picker_manage_integrations" = "Gérer les intégrations…"; "service_terms_modal_policy_checkbox_accessibility_hint" = "Cochez pour accepter %@"; +"settings_labs_dm_key_verification" = "Vérification de clé par message direct"; +"settings_labs_cross_signing" = "Signature croisée"; From ae891101c83f56ae72ed61b80a54931dd10c73ae Mon Sep 17 00:00:00 2001 From: Szimszon Date: Fri, 6 Dec 2019 18:09:56 +0000 Subject: [PATCH 027/282] Translated using Weblate (Hungarian) Currently translated at 100.0% (850 of 850 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/hu/ --- Riot/Assets/hu.lproj/Vector.strings | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index 4d59373787..ebcca5dcce 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -944,3 +944,5 @@ // Accessibility "accessibility_checkbox_label" = "jelölőnégyzet"; "service_terms_modal_policy_checkbox_accessibility_hint" = "Az engedélyezéshez jelöld be: %@"; +"settings_labs_dm_key_verification" = "Kulcs ellenőrzés közvetlen üzenetben"; +"settings_labs_cross_signing" = "Kereszt-aláírás"; From 563c148411b18b31dcab5b810c5feba2146ef7f3 Mon Sep 17 00:00:00 2001 From: random Date: Mon, 9 Dec 2019 14:48:47 +0000 Subject: [PATCH 028/282] Translated using Weblate (Italian) Currently translated at 100.0% (850 of 850 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/it/ --- Riot/Assets/it.lproj/Vector.strings | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Riot/Assets/it.lproj/Vector.strings b/Riot/Assets/it.lproj/Vector.strings index dd8d3669cb..30b1e8d507 100644 --- a/Riot/Assets/it.lproj/Vector.strings +++ b/Riot/Assets/it.lproj/Vector.strings @@ -914,3 +914,5 @@ "accessibility_checkbox_label" = "checkbox"; "widget_picker_manage_integrations" = "Gestisci integrazioni..."; "service_terms_modal_policy_checkbox_accessibility_hint" = "Seleziona per accettare %@"; +"settings_labs_dm_key_verification" = "Verifica chiave via messaggio diretto"; +"settings_labs_cross_signing" = "Firma incrociata"; From db678040644caff65d43758181199b42d1440334 Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Wed, 11 Dec 2019 13:00:39 +0000 Subject: [PATCH 029/282] Translated using Weblate (Albanian) Currently translated at 99.4% (845 of 850 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/sq/ --- Riot/Assets/sq.lproj/Vector.strings | 34 +++++++++++++++-------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/Riot/Assets/sq.lproj/Vector.strings b/Riot/Assets/sq.lproj/Vector.strings index 9918d92a09..e07ff2de99 100644 --- a/Riot/Assets/sq.lproj/Vector.strings +++ b/Riot/Assets/sq.lproj/Vector.strings @@ -602,18 +602,18 @@ "key_backup_recover_title" = "Mesazhe të Sigurt"; "key_backup_recover_empty_backup_title" = "Kopjeruajtje e zbrazët"; "key_backup_recover_empty_backup_message" = "S’ka kyç për rikthim"; -"key_backup_recover_from_passphrase_info" = "Që të shkyçni historikun e mesazheve tuaj të sigurt përdorni frazëkalimin tuaj të rikthimeve"; +"key_backup_recover_from_passphrase_info" = "Që të shkyçni historikun e mesazheve tuaj të sigurt përdorni frazëkalimin tuaj të rimarrjeve"; "key_backup_recover_from_passphrase_passphrase_title" = "Jepeni"; "key_backup_recover_from_passphrase_passphrase_placeholder" = "Jepni Frazëkalimin"; "key_backup_recover_from_passphrase_recover_action" = "Shkyçeni Historikun"; -"key_backup_recover_from_passphrase_lost_passphrase_action_part1" = "Nuk e dini frazëkalimin tuaj të rikthimeve? Mundeni të "; -"key_backup_recover_from_passphrase_lost_passphrase_action_part2" = "përdorni kyçin tuaj të rikthimeve"; +"key_backup_recover_from_passphrase_lost_passphrase_action_part1" = "Nuk e dini frazëkalimin tuaj të rimarrjeve? Mundeni të "; +"key_backup_recover_from_passphrase_lost_passphrase_action_part2" = "përdorni kyçin tuaj të rimarrjeve"; "key_backup_recover_from_passphrase_lost_passphrase_action_part3" = "."; -"key_backup_recover_from_recovery_key_info" = "Përdorni kyçin tuaj të rikthimeve për të shkyçur historikun tuaj të mesazheve të sigurt"; +"key_backup_recover_from_recovery_key_info" = "Që të shkyçni historikun e mesazheve tuaj të sigurt, përdorni frazëkalimin tuaj të rimarrjeve"; "key_backup_recover_from_recovery_key_recovery_key_title" = "Jepeni"; -"key_backup_recover_from_recovery_key_recovery_key_placeholder" = "Jepni Kyç Rikthimi"; +"key_backup_recover_from_recovery_key_recovery_key_placeholder" = "Jepni Kyç Rimarrjesh"; "key_backup_recover_from_recovery_key_recover_action" = "Shkyçe Historikun"; -"key_backup_recover_from_recovery_key_lost_recovery_key_action" = "Humbët kyçin tuaj të rikthimeve? Te rregullimet mund të caktoni një të ri."; +"key_backup_recover_from_recovery_key_lost_recovery_key_action" = "Humbët kyçin tuaj të rimarrjeve? Te rregullimet mund të caktoni një të ri."; "key_backup_recover_success_info" = "Kopjeruajtja u Rikthye!"; "key_backup_recover_done_action" = "U bë"; "key_backup_setup_banner_title_part1" = "Rregulloni Rikthim Mesazhesh të Sigurt"; @@ -626,22 +626,22 @@ "key_backup_setup_intro_setup_action_without_existing_backup" = "Fillo të përdorësh Kopjeruajtje Kyçesh"; "key_backup_setup_intro_setup_action_with_existing_backup" = "Përdor Kopjeruajtje Kyçesh"; "key_backup_setup_passphrase_title" = "Sigurojeni kopjeruajtjen tuaj me një Frazëkalim"; -"key_backup_setup_passphrase_setup_recovery_key_info" = "Ose, sigurojeni kopjeruajtjen tuaj me një Kyç Rikthimesh, duke e ruajtur këtë diku të parrezikuar."; -"key_backup_setup_passphrase_setup_recovery_key_action" = "(Të mëtejshme) Rregullojeni me një Kyç Rikthimesh"; +"key_backup_setup_passphrase_setup_recovery_key_info" = "Ose, sigurojeni kopjeruajtjen tuaj me një Kyç Rimarrjesh, duke e ruajtur këtë diku të parrezikuar."; +"key_backup_setup_passphrase_setup_recovery_key_action" = "(Të mëtejshme) Rregullojeni me një Kyç Rimarrjesh"; "key_backup_setup_success_title" = "Sukses!"; // Success from passphrase -"key_backup_setup_success_from_passphrase_info" = "Po bëhet kopjeruajtja për kyçet tuaj.\n\nKyçi juaj i rikthimeve është një lloj rrjeti sigurie - mund ta përdorni për të rifituar hyrje te mesazhet tuaj të fshehtëzuar, nëse harroni frazëkalimin tuaj.\n\nMbajeni kyçin tuaj të rikthimeve diku shumë të sigurt, bie fjala, nën një përgjegjës fjalëkalimesh (ose në një kasafortë)."; -"key_backup_setup_success_from_passphrase_save_recovery_key_action" = "Ruani Kyç Rikthimesh"; +"key_backup_setup_success_from_passphrase_info" = "Po bëhet kopjeruajtja për kyçet tuaj.\n\nKyçi juaj i rimarrjeve është një lloj mase sigurie - mund ta përdorni për të rifituar hyrje te mesazhet tuaj të fshehtëzuar, nëse harroni frazëkalimin tuaj.\n\nMbajeni kyçin tuaj të rimarrjeve diku shumë të sigurt, bie fjala, nën një përgjegjës fjalëkalimesh (ose në një kasafortë)."; +"key_backup_setup_success_from_passphrase_save_recovery_key_action" = "Ruani Kyç Rimarrjesh"; "key_backup_setup_success_from_passphrase_done_action" = "U krye"; // Success from recovery key -"key_backup_setup_success_from_recovery_key_info" = "Po bëhet kopjeruajtja për kyçet tuaj.\n\nBëni një kopje të këtij kyçi rikthimesh dhe mbajeni të parrezikuar."; -"key_backup_setup_success_from_recovery_key_recovery_key_title" = "Kyç Rikthimesh"; +"key_backup_setup_success_from_recovery_key_info" = "Po bëhet kopjeruajtja për kyçet tuaj.\n\nBëni një kopje të këtij kyçi rimarrjesh dhe mbajeni të parrezikuar."; +"key_backup_setup_success_from_recovery_key_recovery_key_title" = "Kyç Rimarrjesh"; "key_backup_setup_success_from_recovery_key_make_copy_action" = "Bëni një Kopje"; "key_backup_setup_success_from_recovery_key_made_copy_action" = "Kam bërë një kopje"; -"key_backup_recover_invalid_passphrase_title" = "Frazëkalim Rikthimi i Pasaktë"; -"key_backup_recover_invalid_passphrase" = "S’u shfshehtëzua dot kopjeruajtja me këtë frazëkalim: ju lutemi, verifikoni që dhatë frazëkalimin e duhur të rikthimeve."; -"key_backup_recover_invalid_recovery_key_title" = "Mospërputhje Kyçesh Rikthimi"; -"key_backup_recover_invalid_recovery_key" = "Nuk u shfshehtëzua dot kopjeruajtja me këtë kyç: ju lutemi, verifikoni që dhatë kyçin e duhur të rikthimeve."; +"key_backup_recover_invalid_passphrase_title" = "Frazëkalim Rimarrjeje i Pasaktë"; +"key_backup_recover_invalid_passphrase" = "S’u shfshehtëzua dot kopjeruajtja me këtë frazëkalim: ju lutemi, verifikoni që dhatë frazëkalimin e duhur të rimarrjeve."; +"key_backup_recover_invalid_recovery_key_title" = "Mospërputhje Kyçesh Rimarrjeje"; +"key_backup_recover_invalid_recovery_key" = "Nuk u shfshehtëzua dot kopjeruajtja me këtë kyç: ju lutemi, verifikoni që dhatë kyçin e duhur të rimarrjeve."; "key_backup_setup_banner_title" = "Mos humbni kurrë mesazhe të fshehtëzuar"; "key_backup_setup_banner_subtitle" = "Fillo të përdorësh Kopjeruajtje Kyçesh"; "key_backup_recover_banner_title" = "Mos humbni kurrë mesazhe të fshehtëzuar"; @@ -933,3 +933,5 @@ "room_widget_permission_widget_id_permission" = "ID Widget-i"; "room_widget_permission_room_id_permission" = "ID Dhome"; "service_terms_modal_policy_checkbox_accessibility_hint" = "I vini shenjë që të pranohet %@"; +"settings_labs_dm_key_verification" = "Verifikim kyçesh përmes mesazhi të drejtpërdrejtë"; +"settings_labs_cross_signing" = "Nënshkrim kryq"; From 7ec48f13a21b0dd9fb098d26199ea244d426c0e3 Mon Sep 17 00:00:00 2001 From: Kaa Jii Date: Thu, 12 Dec 2019 16:49:31 +0000 Subject: [PATCH 030/282] Translated using Weblate (Italian) Currently translated at 100.0% (850 of 850 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/it/ --- Riot/Assets/it.lproj/Vector.strings | 314 ++++++++++++++-------------- 1 file changed, 157 insertions(+), 157 deletions(-) diff --git a/Riot/Assets/it.lproj/Vector.strings b/Riot/Assets/it.lproj/Vector.strings index 30b1e8d507..90359ddb51 100644 --- a/Riot/Assets/it.lproj/Vector.strings +++ b/Riot/Assets/it.lproj/Vector.strings @@ -1,7 +1,7 @@ // Titles "title_home" = "Home"; "title_favourites" = "Preferiti"; -"title_people" = "Persone"; +"title_people" = "Chat dirette"; "title_rooms" = "Stanze"; "store_full_description" = "Comunica, a modo tuo.\n\nUn'app di messaggistica, sotto il tuo controllo e interamente flessibile. Riot ti permette di comunicare a modo tuo. Creata per [matrix] - lo standard per le comunicazioni aperte, decentralizzate.\n\nOttieni un account matrix.org gratuito, ottieni il tuo server su https://modular.im, o usa un altro server Matrix.\n\nPerché scegliere Riot.im?\n\n• COMUNICAZIONE COMPLETA: crea stanze per i tuoi team, i tuoi amici, la tua comunità - come preferisci! Chatta, condividi file, aggiungi widget e fai videochiamate vocali - tutto gratuito.\n\n• GRANDI INTEGRAZIONI: usa Riot.im con gli strumenti che conosci ed ami. Con Riot.im puoi addirittura chattare con utenti e gruppi su altre applicazioni di messaggistica.\n\n• PRIVATO E SICURO: tieni segrete le tue conversazioni. Una crittografia end-to-end allo stato dell'arte assicura che le comunicazioni private restino tali.\n\n• APERTO, NON CHIUSO: open source e costruito su Matrix. Possiedi i tuoi dati ospitando il tuo server personale, o scegliendone uno di cui ti fidi.\n\n• OVUNQUE TU SIA: resta in contatto ovunque tu sia con la cronologia dei messaggi totalmente sincronizzata tra i tuoi dispositivi ed online su https://riot.im."; "title_groups" = "Comunità"; @@ -12,12 +12,12 @@ "invite" = "Invita"; "cancel" = "Annulla"; "save" = "Salva"; -"join" = "Unisciti"; +"join" = "Entra"; "decline" = "Rifiuta"; "accept" = "Accetto"; "preview" = "Anteprima"; "rename" = "Rinomina"; -"collapse" = "collassa"; +"collapse" = "riduci"; "send_to" = "Invia a %@"; // Authentication "auth_login" = "Accedi"; @@ -33,7 +33,7 @@ "auth_email_placeholder" = "Indirizzo email"; "auth_optional_phone_placeholder" = "Numero di telefono (opzionale)"; "auth_phone_placeholder" = "Numero di telefono"; -"auth_repeat_password_placeholder" = "Ripeti password"; +"auth_repeat_password_placeholder" = "Ripeti la password"; "auth_repeat_new_password_placeholder" = "Conferma la nuova password"; "auth_home_server_placeholder" = "URL (es. https://matrix.org)"; "auth_identity_server_placeholder" = "URL (es. https://vector.im)"; @@ -55,9 +55,9 @@ "auth_forgot_password" = "Password dimenticata?"; "auth_email_validation_message" = "Per favore controlla la tua email per proseguire la registrazione"; "auth_msisdn_validation_title" = "In attesa di verifica"; -"auth_msisdn_validation_message" = "Un SMS è stato spedito con il codice di attivazione. Per favore inserisci il codice qui sotto."; +"auth_msisdn_validation_message" = "Ti è stato spedito un SMS con il codice di attivazione. Per favore inserisci il codice qui sotto."; "auth_msisdn_validation_error" = "Impossibile verificare il numero di telefono."; -"auth_reset_password_message" = "Per ripristinare la password, inserisci l'indirizzo email associato al tuo account:"; +"auth_reset_password_message" = "Per ripristinare la password inserisci l'indirizzo email associato al tuo account:"; // String for App Store "store_short_description" = "Conversazioni sicure e decentralizzate"; // Actions @@ -67,33 +67,33 @@ "create" = "Crea"; "start" = "Inizia"; "retry" = "Riprova"; -"on" = "Attivo"; -"off" = "Disattivo"; +"on" = "On"; +"off" = "Off"; "camera" = "Fotocamera"; -"voice" = "Voce"; +"voice" = "Audio"; "video" = "Video"; -"active_call" = "Chiamata attiva"; -"active_call_details" = "Chiamata attiva (%@)"; +"active_call" = "Chiamata in corso"; +"active_call_details" = "Chiamata in corso (%@)"; "later" = "Più avanti"; "sending" = "Inviando"; "auth_submit" = "Invia"; "auth_login_single_sign_on" = "Accedi con single sign-on"; -"auth_untrusted_id_server" = "Il server identità non è affidabile"; +"auth_untrusted_id_server" = "L'affidabilità dell'Identity Server non è stata accertata"; "auth_password_dont_match" = "Le password non corrispondono"; "auth_username_in_use" = "Nome utente in uso"; "auth_email_not_found" = "Invio dell'email fallito: Questo indirizzo email non è stato trovato"; -"auth_use_server_options" = "Usa opzioni server personalizzate (avanzate)"; -"auth_recaptcha_message" = "Questo server home vorrebbe assicurarsi che tu non sia un robot"; -"auth_reset_password_missing_email" = "L'indirizzo email associato al tuo account deve essere inserito."; -"auth_reset_password_missing_password" = "Una nuova password deve essere inserita."; -"auth_reset_password_email_validation_message" = "Un'email è stata inviata a %s. Appena avrai seguito il link lì contenuto, clicca qui sotto."; +"auth_use_server_options" = "Usa le opzioni personalizzate del Server (avanzate)"; +"auth_recaptcha_message" = "Questo Home Server vuol esser certo che tu non sia un robot"; +"auth_reset_password_missing_email" = "Va inserito l'indirizzo email associato al tuo account."; +"auth_reset_password_missing_password" = "Va inserita una nuova password."; +"auth_reset_password_email_validation_message" = "Un'email è stata inviata a %s. Dopo aver cliccato il link che contiene, clicca qui sotto."; "auth_reset_password_next_step_button" = "Ho verificato il mio indirizzo email"; "auth_reset_password_error_unauthorized" = "Verifica indirizzo email fallita: assicurati di aver cliccato sul link contenuto nell'email"; -"auth_reset_password_error_not_found" = "Il tuo indirizzo email non sembra associato a nessun ID Matrix su questo server home."; -"auth_reset_password_success_message" = "La tua password è stata ripristinata.\n\n Sei stato disconnesso da tutti i dispositivi e non riceverai più alcuna notifica. Per riabilitare le notifiche, riconnettiti su ciascun dispositivo."; -"auth_add_email_and_phone_warning" = "La registrazione con email e numero di telefono in una volta sola non è ancora supportata (relative API in sviluppo). Sarà utilizzato solo il numero di telefono. Puoi aggiungere la tua email al tuo profilo dalle impostazioni."; -"auth_accept_policies" = "Per favore, rivedere e accettare le politiche di questo server home:"; -"auth_autodiscover_invalid_response" = "Risposta alla scoperta di un homeserver non valida"; +"auth_reset_password_error_not_found" = "Il tuo indirizzo email non sembra associato a nessun ID utente registrato su questo Home Server."; +"auth_reset_password_success_message" = "La tua password è stata ripristinata.\n\nSei stato disconnesso da tutti i dispositivi e non riceverai più alcuna notifica. Per riabilitare le notifiche, riconnettiti su ciascun dispositivo."; +"auth_add_email_and_phone_warning" = "Al momento non è ancora possibile registrarsi contemporaneamente con indirizzo email e numero di telefono e quindi verrà utilizzato solo il numero di telefono. Puoi aggiungere la tua email al tuo profilo dall menù Impostazioni."; +"auth_accept_policies" = "Per favore, rileggi e accetta i termini di servizio di questo Home Server:"; +"auth_autodiscover_invalid_response" = "Risposta Home Server non valida"; // Chat creation "room_creation_title" = "Nuova chat"; "room_creation_account" = "Account"; @@ -108,13 +108,13 @@ "room_creation_make_public_prompt_msg" = "Sei sicuro di voler rendere pubblica questa chat? Chiunque potrà leggere i tuoi messaggi e unirsi alla chat."; "room_creation_keep_private" = "Mantieni privata"; "room_creation_make_private" = "Rendi privata"; -"room_creation_wait_for_creation" = "Una stanza sta già venendo creata. Per favore attendi."; +"room_creation_wait_for_creation" = "Una stanza è già in fase di creazione. Per favore attendi."; "room_creation_invite_another_user" = "Cerca / invita per ID utente, nome o email"; // Room recents "room_recents_directory_section" = "ELENCO STANZE"; "room_recents_directory_section_network" = "Rete"; "room_recents_favourites_section" = "PREFERITI"; -"room_recents_people_section" = "PERSONE"; +"room_recents_people_section" = "CHAT DIRETTE"; "room_recents_conversations_section" = "STANZE"; "room_recents_no_conversation" = "Nessuna stanza"; "room_recents_low_priority_section" = "BASSA PRIORITÀ"; @@ -124,7 +124,7 @@ "room_recents_create_empty_room" = "Crea stanza"; "room_recents_join_room" = "Entra nella stanza"; "room_recents_join_room_title" = "Entra in una stanza"; -"room_recents_join_room_prompt" = "Digita ID o soprannome (alias)"; +"room_recents_join_room_prompt" = "Digita ID stanza o il suo nome"; // People tab "people_invites_section" = "INVITI"; "people_conversation_section" = "CONVERSAZIONI"; @@ -144,19 +144,19 @@ "search_no_result" = "Nessun risultato"; "search_in_progress" = "Ricerca…"; // Directory -"directory_cell_title" = "Esplora elenco"; +"directory_cell_title" = "Esplora l'elenco"; "directory_cell_description" = "%tu stanze"; "directory_search_results_title" = "Esplora risultati elenco"; "directory_search_results" = "%tu risultati trovati per %@"; "directory_search_results_more_than" = ">%tu risultati trovati per %@"; "directory_searching_title" = "Ricerca negli elenchi…"; -"directory_search_fail" = "Fallita la ricerca di informazioni"; +"directory_search_fail" = "La ricerca di informazioni è fallita"; // Contacts "contacts_address_book_section" = "CONTATTI LOCALI"; "contacts_address_book_matrix_users_toggle" = "Solo utenti Matrix"; "contacts_address_book_no_contact" = "Nessun contatto locale"; -"contacts_address_book_permission_required" = "Permesso richiesto per accedere ai contatti"; -"contacts_address_book_permission_denied" = "Non hai permesso a Riot di accedere ai tuoi contatti"; +"contacts_address_book_permission_required" = "Riot deve essere autorizzato per poter accedere alla Rubrica locale"; +"contacts_address_book_permission_denied" = "Non hai autorizzato Riot ad accedere alla Rubrica locale"; "contacts_user_directory_section" = "ELENCO UTENTI"; "contacts_user_directory_offline_section" = "ELENCO UTENTI (offline)"; // Chat participants @@ -164,17 +164,17 @@ "room_participants_add_participant" = "Aggiungi membro"; "room_participants_one_participant" = "1 membro"; "room_participants_multi_participants" = "%d membri"; -"room_participants_leave_prompt_title" = "Lascia stanza"; +"room_participants_leave_prompt_title" = "Esci dalla stanza"; "room_participants_leave_prompt_msg" = "Sei sicuro di voler uscire dalla stanza?"; "room_participants_remove_prompt_title" = "Conferma"; "room_participants_remove_prompt_msg" = "Sei sicuro di voler rimuovere %@ da questa chat?"; "room_participants_remove_third_party_invite_msg" = "Finché non esistono le API, la rimozione di inviti di terze parti non è possibile"; "room_participants_invite_prompt_title" = "Conferma"; "room_participants_invite_prompt_msg" = "Sei sicuro di voler invitare %@ in questa chat?"; -"room_participants_filter_room_members" = "Filtra i membri della stanza"; +"room_participants_filter_room_members" = "Cerca tra i membri della stanza"; "room_participants_invite_another_user" = "Cerca / invita per ID utente, nome o email"; "room_participants_invite_malformed_id_title" = "Errore durante l'invito"; -"room_participants_invite_malformed_id" = "ID incorretto. Dovrebbe essere un indirizzo email o un ID Matrix come '@localpart:domain'"; +"room_participants_invite_malformed_id" = "ID incorretto. Dovrebbe essere un indirizzo email o un ID utente tipo '@localpart:domain'"; "room_participants_invited_section" = "INVITATI"; "room_participants_online" = "Online"; "room_participants_offline" = "Offline"; @@ -182,23 +182,23 @@ "room_participants_idle" = "Inattivo"; "room_participants_now" = "adesso"; "room_participants_ago" = "fa"; -"room_participants_action_section_admin_tools" = "Strumenti admin"; +"room_participants_action_section_admin_tools" = "Strumenti Admin"; "room_participants_action_section_direct_chats" = "Chat dirette"; "room_participants_action_section_devices" = "Dispositivi"; "room_participants_action_section_other" = "Altro"; "room_participants_action_invite" = "Invita"; -"room_participants_action_leave" = "Lascia questa stanza"; +"room_participants_action_leave" = "Esci da questa stanza"; "room_participants_action_remove" = "Rimuovi da questa stanza"; -"room_participants_action_ban" = "Bandisci da questa stanza"; -"room_participants_action_unban" = "Togli il bando"; +"room_participants_action_ban" = "Banna da questa stanza"; +"room_participants_action_unban" = "Rimuovi il ban"; "room_participants_action_ignore" = "Nascondi tutti i messaggi di questo utente"; "room_participants_action_unignore" = "Mostra tutti i messaggi di questo utente"; "room_participants_action_set_default_power_level" = "Ripristina ad utente normale"; -"room_participants_action_set_moderator" = "Rendi moderatore"; -"room_participants_action_set_admin" = "Rendi amministratore"; +"room_participants_action_set_moderator" = "Nomina moderatore"; +"room_participants_action_set_admin" = "Nomina amministratore"; "room_participants_action_start_new_chat" = "Inizia nuova chat"; -"room_participants_action_start_voice_call" = "Avvia chiamata vocale"; -"room_participants_action_start_video_call" = "Avvia videochiamata"; +"room_participants_action_start_voice_call" = "Avvia chiamata audio"; +"room_participants_action_start_video_call" = "Avvia chiamata video"; "room_participants_action_mention" = "Citazione"; // Chat "room_jump_to_first_unread" = "Vai al primo messaggio non letto"; @@ -207,62 +207,62 @@ "room_one_user_is_typing" = "%@ sta scrivendo…"; "room_two_users_are_typing" = "%@ e %@ stanno scrivendo…"; "room_many_users_are_typing" = "%@, %@ e altri stanno scrivendo…"; -"room_message_placeholder" = "Invia un messaggio (non criptato)…"; -"room_message_reply_to_placeholder" = "Invia una risposta (non criptata)…"; +"room_message_placeholder" = "Invia un messaggio (non cifrato)…"; +"room_message_reply_to_placeholder" = "Invia una risposta (non cifrata)…"; "room_message_unable_open_link_error_message" = "Impossibile aprire il link."; "room_do_not_have_permission_to_post" = "Non hai il permesso di pubblicare in questa stanza"; -"encrypted_room_message_placeholder" = "Invia un messaggio criptato…"; -"encrypted_room_message_reply_to_placeholder" = "Invia una risposta criptata…"; +"encrypted_room_message_placeholder" = "Invia un messaggio cifrato…"; +"encrypted_room_message_reply_to_placeholder" = "Invia una risposta cifrata…"; "room_message_short_placeholder" = "Invia un messaggio…"; "room_message_reply_to_short_placeholder" = "Invia una risposta…"; "room_offline_notification" = "La connessione al server è stata persa."; "room_unsent_messages_notification" = "Messaggi non inviati. %1$s o %2$s ora?"; -"room_unsent_messages_unknown_devices_notification" = "Messaggi non inviati a causa di dispositivi sconosciuti qui presenti. %1$s o %2$s ora?"; -"room_ongoing_conference_call" = "Chiamata di conferenza in corso. Unisciti come %@ o %@."; -"room_ongoing_conference_call_with_close" = "Chiamata di conferenza in corso. Unisciti come %@ o %@. %@."; +"room_unsent_messages_unknown_devices_notification" = "Messaggi non inviati a causa della presenza di dispositivi sconosciuti. %1$s o %2$s ora?"; +"room_ongoing_conference_call" = "Avvio conferenza. Unisciti come %@ o %@."; +"room_ongoing_conference_call_with_close" = "Avvio conferenza. Unisciti come %@ o %@. %@."; "room_ongoing_conference_call_close" = "Chiudi"; -"room_conference_call_no_power" = "Hai bisogno dei permessi per gestire le chiamate di gruppo in questa stanza"; -"room_prompt_resend" = "Rinvia tutto"; +"room_conference_call_no_power" = "Non hai permessi sufficienti per avviare una conferenza in questa stanza"; +"room_prompt_resend" = "Reinvia tutto"; "room_prompt_cancel" = "annulla tutto"; "room_resend_unsent_messages" = "Reinvia i messaggi non spediti"; -"room_delete_unsent_messages" = "Elimina messaggi non spediti"; +"room_delete_unsent_messages" = "Elimina i messaggi non spediti"; "room_event_action_copy" = "Copia"; "room_event_action_quote" = "Cita"; "room_event_action_redact" = "Rimuovi"; "room_event_action_more" = "Altro"; "room_event_action_share" = "Condividi"; -"room_event_action_permalink" = "Collegamento permanente"; -"room_event_action_view_source" = "Vedi sorgente"; -"room_event_action_view_decrypted_source" = "Vedi sorgente decriptata"; -"room_event_action_report" = "Segnala contenuto"; +"room_event_action_permalink" = "Permalink"; +"room_event_action_view_source" = "Vedi il codice sorgente"; +"room_event_action_view_decrypted_source" = "Vedi il codice sorgente decifrato"; +"room_event_action_report" = "Segnala il contenuto"; "room_event_action_report_prompt_reason" = "Motivo della segnalazione"; "room_event_action_kick_prompt_reason" = "Motivo per cui rimuovi questo utente"; "room_event_action_ban_prompt_reason" = "Motivo per cui banni questo utente"; -"room_event_action_report_prompt_ignore_user" = "Vuoi nascondere tutti i messaggi da questo utente?"; +"room_event_action_report_prompt_ignore_user" = "Vuoi nascondere tutti i messaggi di questo utente?"; "room_event_action_save" = "Salva"; "room_event_action_resend" = "Reinvia"; "room_event_action_delete" = "Elimina"; "room_event_action_cancel_send" = "Annulla invio"; "room_event_action_cancel_download" = "Annulla download"; -"room_event_action_view_encryption" = "Informazioni crittografia"; -"room_warning_about_encryption" = "La crittografia da-utente-a-utente è in fase sperimentale e potrebbe non esser ancora affidabile.\n\nNon dovesti ancora farci affidamento per proteggere i tuoi dati.\n\nI dispositivi non potranno decrittare la cronologia dei messaggi precedenti all'entrata nella stanza.\n\nI messaggi crittografati non saranno visibili dalle applicazioni che non hanno ancora implementato la crittografia."; +"room_event_action_view_encryption" = "Informazioni sulla crittografia"; +"room_warning_about_encryption" = "La crittografia E2E è in fase sperimentale e potrebbe non esser ancora affidabile.\n\nNon dovesti ancora farci affidamento per proteggere i tuoi dati.\n\nI dispositivi non potranno decifrare i messaggi precedenti alla tua entrata nella stanza.\n\nI messaggi cifrati non saranno visibili dalle applicazioni per Matrix che non hanno ancora implementato la crittografia."; "room_event_failed_to_send" = "Invio fallito"; "room_action_send_photo_or_video" = "Invia foto o video"; "room_action_send_sticker" = "Invia sticker"; -"room_replacement_information" = "Questa stanza è stata sostituita e non è più attiva."; +"room_replacement_information" = "Questa stanza è stata sostituita da un'altra e non è più attiva."; "room_replacement_link" = "La conversazione continua qui."; -"room_predecessor_information" = "Questa stanza è la continuazione di un'altra conversazione."; -"room_predecessor_link" = "Clicca per vedere messaggi più vecchi."; +"room_predecessor_information" = "Questa stanza contiene una conversazione cominciata altrove."; +"room_predecessor_link" = "Clicca per vedere i messaggi più vecchi."; "room_resource_limit_exceeded_message_contact_1" = " Per favore "; "room_resource_limit_exceeded_message_contact_2_link" = "contatta l'amministratore del servizio"; "room_resource_limit_exceeded_message_contact_3" = " per continuare ad usare questo servizio."; -"room_resource_usage_limit_reached_message_1_default" = "Questo server home ha superato uno dei limiti delle risorse, pertanto "; -"room_resource_usage_limit_reached_message_1_monthly_active_user" = "Questo server home ha raggiunto il limite mensile di utenti attivi, pertanto "; +"room_resource_usage_limit_reached_message_1_default" = "Questo Home Server ha superato uno dei limiti delle risorse, pertanto "; +"room_resource_usage_limit_reached_message_1_monthly_active_user" = "Questo Home Server ha raggiunto il limite mensile di utenti attivi, pertanto "; "room_resource_usage_limit_reached_message_2" = "alcuni utenti non potranno accedere."; "room_resource_usage_limit_reached_message_contact_3" = " per aumentare questo limite."; // Unknown devices "unknown_devices_alert_title" = "La stanza contiene dispositivi sconosciuti"; -"unknown_devices_alert" = "Questa stanza contiene dispositivi sconosciuti che non sono stati verificati.\nCiò significa che non esiste alcuna garanzia che i dispositivi siano davvero quelli di chi dice di possederli.\nPrima di continuare si consiglia di effettuare la verifica di ogni dispositivo, ma se vuoi è comunque possibile rispedire il messaggio senza fare alcuna verifica."; +"unknown_devices_alert" = "Questa stanza contiene dispositivi sconosciuti che non sono stati verificati.\nNon v'è alcuna garanzia che le persone che utilizzano quei dispositivi siano davvero chi dicono d'essere.\nSi consiglia di verificare ogni dispositivo prima di continuare, ma se si preferisce, è comunque possibile inviare ugualmente il messaggio anche senza la verifica."; "unknown_devices_send_anyway" = "Invia comunque"; "unknown_devices_call_anyway" = "Chiama comunque"; "unknown_devices_answer_anyway" = "Rispondi comunque"; @@ -276,20 +276,20 @@ "room_title_members" = "%@ membri"; "room_title_one_member" = "1 membro"; // Room Preview -"room_preview_invitation_format" = "Sei stato invitato ad unirti in questa stanza da %@"; -"room_preview_subtitle" = "Questa è l'anteprima della stanza. Le interazioni con la stanza sono disabilitate."; -"room_preview_unlinked_email_warning" = "Questo invito è stato spedito da %@, che non è associato a questo account. È possibile che tu voglia connetterti con un altro account o aggiungere questo indirizzo email al tuo account."; -"room_preview_try_join_an_unknown_room" = "Stai provando ad accedere a %@. Desideri entrare per partecipare alla discussione?"; +"room_preview_invitation_format" = "Sei stato invitato ad entrare in questa stanza da %@"; +"room_preview_subtitle" = "Questa è l'anteprima della stanza. Le interazioni sono disabilitate."; +"room_preview_unlinked_email_warning" = "Questo invito è stato spedito a %@, che non è associato a questo account. Puoi aggiungere questa email al tuo account o provare ad accedere con un account differente."; +"room_preview_try_join_an_unknown_room" = "Stai provando ad accedere a %s. Desideri entrare per partecipare alla discussione?"; "room_preview_try_join_an_unknown_room_default" = "una stanza"; // Settings "settings_title" = "Impostazioni"; -"account_logout_all" = "Sconnetti tutti gli account"; -"settings_config_no_build_info" = "Nessuna informazione di build"; +"account_logout_all" = "Disconnetti tutti gli account"; +"settings_config_no_build_info" = "Nessuna informazione su questa versione"; "settings_mark_all_as_read" = "Segna tutti i messaggi come letti"; "settings_report_bug" = "Segnala errore"; "settings_clear_cache" = "Elimina cache"; -"settings_config_home_server" = "L'Homeserver è %@"; -"settings_config_identity_server" = "L'Identity server è %@"; +"settings_config_home_server" = "L'Home Server è %@"; +"settings_config_identity_server" = "L'Identity Server è %@"; "settings_config_user_id" = "Connesso come %@"; "settings_user_settings" = "IMPOSTAZIONI UTENTE"; "settings_notifications_settings" = "IMPOSTAZIONI NOTIFICHE"; @@ -300,14 +300,14 @@ "settings_advanced" = "AVANZATE"; "settings_other" = "ALTRO"; "settings_labs" = "LABORATORIO"; -"settings_flair" = "Mostra predisposizione se permesso"; +"settings_flair" = "Mostra la predisposizione quando è consentito"; "settings_devices" = "DISPOSITIVI"; "settings_cryptography" = "CRITTOGRAFIA"; "settings_key_backup" = "BACKUP DELLE CHIAVI"; "settings_deactivate_account" = "DISATTIVA ACCOUNT"; "settings_sign_out" = "Disconnetti"; "settings_sign_out_confirmation" = "Sei sicuro?"; -"settings_sign_out_e2e_warn" = "Perderai le tue chiavi di crittografia da-utente-a-utente. Questo significa che non potrai più leggere i vecchi messaggi nelle stanze crittografate su questo dispositivo."; +"settings_sign_out_e2e_warn" = "Perderai le tue chiavi crittografiche E2E. Questo significa che non potrai più leggere i vecchi messaggi nelle stanze cifrate su questo dispositivo."; "settings_profile_picture" = "Immagine profilo"; "settings_display_name" = "Nome visualizzato"; "settings_first_name" = "Nome"; @@ -324,13 +324,13 @@ "settings_night_mode" = "Modalità notte"; "settings_fail_to_update_profile" = "Errore nell'aggiornamento del profilo"; "settings_enable_push_notif" = "Notifiche per questo dispositivo"; -"settings_show_decrypted_content" = "Mostra contenuto decrittato"; -"settings_global_settings_info" = "Le impostazioni globali di notifica sono disponibili nel tuo %@ web client"; +"settings_show_decrypted_content" = "Mostra il contenuto decifrato"; +"settings_global_settings_info" = "Le impostazioni di notifica avanzate sono disponibili nel tuo %@ web client"; "settings_pin_rooms_with_missed_notif" = "Segna le stanze con notifiche perse"; "settings_pin_rooms_with_unread" = "Segna le stanze con messaggi non letti"; "settings_on_denied_notification" = "Le notifiche non sono permesse per %@, abilitale nelle impostazioni del tuo dispositivo"; "settings_enable_callkit" = "Chiamate integrate"; -"settings_callkit_info" = "Ricevi le chiamate in arrivo nel blocca schermo. Mostra le chiamate Riot nella cronologia di chiamate del dispositivo. Se iCloud è attivo, questa cronologia sarà condivisa con Apple."; +"settings_callkit_info" = "Ricevi le chiamate in arrivo sul blocca schermo. Mostra le chiamate Riot nella cronologia di chiamate del dispositivo. Se iCloud è attivo, questa cronologia sarà condivisa con Apple."; "settings_ui_language" = "Lingua"; "settings_ui_theme" = "Tema"; "settings_ui_theme_auto" = "Automatico"; @@ -341,12 +341,12 @@ "settings_ui_theme_picker_message" = "\"Automatico\" usa l'impostazione \"Inverti Colori\" del tuo dispositivo"; "settings_unignore_user" = "Mostrare tutti i messaggi da %@?"; "settings_contacts_discover_matrix_users" = "Usa email e numeri di telefono per trovare utenti"; -"settings_contacts_phonebook_country" = "Nazione rubrica telefonica"; -"settings_labs_e2e_encryption" = "Crittografia da-utente-a-utente"; +"settings_contacts_phonebook_country" = "Prefisso telefonico internazionale"; +"settings_labs_e2e_encryption" = "Crittografia E2E"; "settings_labs_e2e_encryption_prompt_message" = "Per finire la configurazione della crittografia devi rieseguire l'accesso."; -"settings_labs_room_members_lazy_loading" = "Caricamento posticipato dei membri della stanza"; -"settings_labs_room_members_lazy_loading_error_message" = "Il tuo Homeserver non supporta ancora il caricamento intelligente dei membri delle stanze. Prova in seguito."; -"settings_labs_create_conference_with_jitsi" = "Crea una videoconferenza con jitsi"; +"settings_labs_room_members_lazy_loading" = "Caricamento differito dei membri della stanza"; +"settings_labs_room_members_lazy_loading_error_message" = "Il tuo Home Server non supporta ancora il caricamento differito dei membri delle stanze. Prova più avanti."; +"settings_labs_create_conference_with_jitsi" = "Avvia una conferenza usando Jitsi"; "settings_version" = "Versione %@"; "settings_olm_version" = "Versione Olm %@"; "settings_copyright" = "Copyright"; @@ -357,41 +357,41 @@ "settings_privacy_policy_url" = "https://riot.im/privacy"; "settings_third_party_notices" = "Avvisi di terze parti"; "settings_send_crash_report" = "Invia dati di utilizzo anonimi"; -"settings_enable_rageshake" = "Agita con rabbia per segnalare un errore"; +"settings_enable_rageshake" = "Per segnalare un errore agita il dispositivo con rabbia"; "settings_old_password" = "vecchia password"; "settings_new_password" = "nuova password"; "settings_confirm_password" = "conferma password"; "settings_fail_to_update_password" = "Aggiornamento password fallito"; "settings_password_updated" = "La tua password è stata aggiornata"; "settings_crypto_device_name" = "Nome pubblico dispositivo: "; -"settings_crypto_device_id" = "\nID Dispositivo: "; +"settings_crypto_device_id" = "\nID dispositivo: "; "settings_crypto_device_key" = "\nChiave dispositivo:\n"; "settings_crypto_export" = "Esporta chiavi"; -"settings_crypto_blacklist_unverified_devices" = "Crittografa solo per i dispositivi verificati"; +"settings_crypto_blacklist_unverified_devices" = "Cifratura solo per i dispositivi verificati"; "settings_deactivate_my_account" = "Disattiva il mio account"; -"settings_key_backup_info" = "I messaggi crittografati sono protetti con la crittografia da-utente-a-utente. Solo tu e il/i destinatario/i avete le chiavi per leggere questi messaggi."; +"settings_key_backup_info" = "I messaggi nelle stanze cifrate sono protetti con crittografia E2E. Solo tu e il/i destinatario/i avete le chiavi crittografiche per leggere questi messaggi."; "settings_key_backup_info_checking" = "Verifica..."; -"settings_key_backup_info_none" = "Nessun Backup programmato per le Chiavi da questo dispositivo."; -"settings_key_backup_info_signout_warning" = "Connetti questo dispositivo al backup chiavi prima di disconnetterti per evitare di perdere eventuali chiavi presenti solo qui."; +"settings_key_backup_info_none" = "Questo dispositivo non sta facendo il Backup delle chiavi."; +"settings_key_backup_info_signout_warning" = "Prima di disconnetterti effettua un Backup delle chiavi per evitare di perdere eventuali chiavi presenti solo questo dispositivo."; "settings_key_backup_info_version" = "Versione backup chiave: %@"; "settings_key_backup_info_algorithm" = "Algoritmo: %@"; -"settings_key_backup_info_valid" = "Questo dispositivo sta eseguendo il backup delle tue chiavi."; -"settings_key_backup_info_not_valid" = "Questo dispositivo non sta eseguendo il Backup delle tue Chiavi, ma é disponibile un vecchio Backup da cui puoi ripristinarle."; +"settings_key_backup_info_valid" = "Questo dispositivo sta eseguendo il Backup delle tue chiavi."; +"settings_key_backup_info_not_valid" = "Questo dispositivo non sta eseguendo il Backup delle tue Chiavi. Però è disponibile un vecchio Backup da cui puoi ripristinarle."; "settings_key_backup_info_progress" = "Backup di %@ chiavi…"; "settings_key_backup_info_progress_done" = "Backup di tutte le chiavi completato"; -"settings_key_backup_info_trust_signature_unknown" = "Il backup ha una firma dal dispositivo con ID: %@"; +"settings_key_backup_info_trust_signature_unknown" = "Il Backup ha una firma dal dispositivo con ID: %@"; "settings_key_backup_info_trust_signature_valid" = "Il backup ha una firma valida da questo dispositivo"; -"settings_key_backup_info_trust_signature_valid_device_verified" = "Il backup ha una firma valida da %@"; -"settings_key_backup_info_trust_signature_valid_device_unverified" = "Il backup ha una firma da %@"; -"settings_key_backup_info_trust_signature_invalid_device_verified" = "Il backup ha una firma non valida da %@"; -"settings_key_backup_info_trust_signature_invalid_device_unverified" = "Il backup ha una firma non valida da %@"; -"settings_key_backup_button_create" = "Inizia ad usare il backup chiavi"; -"settings_key_backup_button_restore" = "Ripristina da backup"; +"settings_key_backup_info_trust_signature_valid_device_verified" = "Il Backup ha una firma valida da %@"; +"settings_key_backup_info_trust_signature_valid_device_unverified" = "Il Backup ha una firma da %@"; +"settings_key_backup_info_trust_signature_invalid_device_verified" = "Il Backup ha una firma non valida da %@"; +"settings_key_backup_info_trust_signature_invalid_device_unverified" = "Il Backup ha una firma non valida da %@"; +"settings_key_backup_button_create" = "Inizia ad usare il Backup delle chiavi"; +"settings_key_backup_button_restore" = "Ripristina da Backup"; "settings_key_backup_button_delete" = "Elimina backup"; -"settings_key_backup_delete_confirmation_prompt_title" = "Elimina backup"; -"settings_key_backup_delete_confirmation_prompt_msg" = "Sei sicuro? Se non hai eseguito un Backup delle Chiavi perderai i tuoi messaggi crittografati."; +"settings_key_backup_delete_confirmation_prompt_title" = "Elimina Backup"; +"settings_key_backup_delete_confirmation_prompt_msg" = "Sei sicuro? Se non hai eseguito un Backup delle chiavi perderai i tuoi messaggi cifrati."; // Room Details -"room_details_title" = "Dettagli canale"; +"room_details_title" = "Dettagli stanza"; "room_details_people" = "Membri"; "room_details_files" = "File"; "room_details_settings" = "Impostazioni"; @@ -407,8 +407,8 @@ "room_details_access_section_anyone_apart_from_guest" = "Chiunque conosca il link della stanza, eccetto gli ospiti"; "room_details_access_section_anyone" = "Chiunque conosca il link della stanza, compresi gli ospiti"; "room_details_access_section_no_address_warning" = "Per poter essere linkata, la stanza deve avere un indirizzo"; -"room_details_access_section_directory_toggle" = "Mostra questa stanza nell'elenco delle stanze"; -"room_details_history_section" = "Chi può leggere la cronologia?"; +"room_details_access_section_directory_toggle" = "Mostra questa stanza nell'elenco delle stanze pubbliche"; +"room_details_history_section" = "Chi può leggere la Timeline?"; "room_details_history_section_anyone" = "Chiunque"; "room_details_history_section_members_only" = "Solo i membri (dal momento in cui questa opzione è stata selezionata)"; "room_details_history_section_members_only_since_invited" = "Solo i membri (dal momento in cui vengono invitati)"; @@ -593,13 +593,13 @@ "key_backup_setup_skip_alert_title" = "Sei sicuro?"; "key_backup_setup_skip_alert_message" = "Se ti disconnetti oppure perdi il dispositivo potresti perdere i tuoi messaggi crittografati."; "key_backup_setup_skip_alert_skip_action" = "Salta"; -"key_backup_setup_intro_title" = "Non perdere mai i messaggi crittografati"; -"key_backup_setup_intro_info" = "I messaggi nelle stanze crittografate sono protetti con crittografia da-utente-a-utente. Solo tu e il/i destinatario/i avete le chiavi per leggere questi messaggi. \n \nFai un backup delle tue chiavi per evitare di perderle."; +"key_backup_setup_intro_title" = "Non perdere mai i messaggi cifrati"; +"key_backup_setup_intro_info" = "I messaggi nelle stanze cifrate sono protetti con crittografia E2E. Solo tu e il/i destinatario/i avete le chiavi per leggere questi messaggi. \n \nFai un backup delle tue chiavi crittografiche per evitare di perderle."; "key_backup_setup_intro_setup_action_without_existing_backup" = "Inizia ad usare il backup chiavi"; "key_backup_setup_intro_manual_export_info" = "(Avanzato)"; "key_backup_setup_intro_manual_export_action" = "Esporta manualmente le chiavi"; "key_backup_setup_passphrase_title" = "Proteggi il tuo backup con una frase d'accesso"; -"key_backup_setup_passphrase_info" = "Salveremo una copia crittografata delle tue chiavi nel tuo Homeserver. Proteggi il tuo backup con una frase di sicurezza per tenerlo sicuro. \n \nPer una massima sicurezza, dovrebbe essere diversa dalla password del tuo account."; +"key_backup_setup_passphrase_info" = "Sul tuo Home Server verrà effettuato un Backup cifrato delle tue chiavi crittograficher. Proteggi il Backup con una password perchè sia al sicuro.\n \nPer una massima sicurezza, la password del Backup dovrebbe essere diversa dalla password del tuo account."; "key_backup_setup_passphrase_passphrase_title" = "Inserisci"; "key_backup_setup_passphrase_passphrase_placeholder" = "Inserisci frase d'accesso"; "key_backup_setup_passphrase_passphrase_valid" = "Bene!"; @@ -613,7 +613,7 @@ "key_backup_setup_passphrase_setup_recovery_key_action" = "(Avanzato) Imposta con chiave di ripristino"; "key_backup_setup_success_title" = "Completato!"; // Success from passphrase -"key_backup_setup_success_from_passphrase_info" = "Backup delle tue chiavi in corso.\n\nLa tua chiave di ripristino è uno strumento di sicurezza - puoi usarla per recuperare l'accesso ai tuoi messaggi crittografati se dimentichi la tua frase d'accesso. \n\nTieni la tua chiave di ripristino in un luogo sicuro, come un password manager (o una cassaforte)."; +"key_backup_setup_success_from_passphrase_info" = "Backup delle tue chiavi in corso.\n\nIl tuo codice di recupero è un'ancora di salvezza - puoi usarlo per riaccedere ai tuoi messaggi cifrati se dimentichi la password. \n\nSalva il tuo codice di recupero in un luogo sicuro, tipo un password manager (o una cassaforte)."; "key_backup_setup_success_from_passphrase_save_recovery_key_action" = "Salva chiave di ripristino"; "key_backup_setup_success_from_passphrase_done_action" = "Fatto"; // Success from recovery key @@ -642,34 +642,34 @@ "key_backup_recover_done_action" = "Fatto"; "key_backup_setup_banner_title" = "Non perdere mai i messaggi crittografati"; "key_backup_setup_banner_subtitle" = "Inizia ad usare il backup chiavi"; -"key_backup_recover_banner_title" = "Non perdere mai i messaggi crittografati"; +"key_backup_recover_banner_title" = "Non perdere mai i messaggi cifrati"; "sign_out_existing_key_backup_alert_title" = "Sei sicuro di volerti disconnettere?"; "sign_out_existing_key_backup_alert_sign_out_action" = "Disconnetti"; -"sign_out_non_existing_key_backup_alert_title" = "Se ti disconnetti ora, perderai l'accesso ai tuoi messaggi crittografati"; +"sign_out_non_existing_key_backup_alert_title" = "Se adesso ti disconnetti, perderai l'accesso ai tuoi messaggi cifrati"; "sign_out_non_existing_key_backup_alert_setup_key_backup_action" = "Inizia ad usare il Backup delle chiavi"; -"sign_out_non_existing_key_backup_alert_discard_key_backup_action" = "Non voglio i miei messaggi crittografati"; -"sign_out_non_existing_key_backup_sign_out_confirmation_alert_title" = "Perderai i tuoi messaggi crittografati"; -"sign_out_non_existing_key_backup_sign_out_confirmation_alert_message" = "Perderai l'accesso ai tuoi messaggi crittografati a meno che non fai il Backup delle tue chiavi prima di disconnetterti."; +"sign_out_non_existing_key_backup_alert_discard_key_backup_action" = "Non voglio i miei messaggi cifrati"; +"sign_out_non_existing_key_backup_sign_out_confirmation_alert_title" = "Perderai i tuoi messaggi cifrati"; +"sign_out_non_existing_key_backup_sign_out_confirmation_alert_message" = "Perderai l'accesso ai tuoi messaggi cifrati a meno che tu faccia il Backup delle chiavi prima di disconnetterti."; "sign_out_non_existing_key_backup_sign_out_confirmation_alert_sign_out_action" = "Disconnetti"; "sign_out_non_existing_key_backup_sign_out_confirmation_alert_backup_action" = "Backup"; -"sign_out_key_backup_in_progress_alert_title" = "Backup chiavi in corso. Se ti disconnetti ora, perderai l'accesso ai tuoi messaggi crittografati."; -"sign_out_key_backup_in_progress_alert_discard_key_backup_action" = "Non voglio i miei messaggi crittografati"; +"sign_out_key_backup_in_progress_alert_title" = "Backup delle chiavi in corso. Se ti disconnetti ora, perderai l'accesso ai tuoi messaggi cifrati."; +"sign_out_key_backup_in_progress_alert_discard_key_backup_action" = "Non voglio i miei messaggi cifrati"; "sign_out_key_backup_in_progress_alert_cancel_action" = "Attendo"; "close" = "Chiudi"; -"auth_forgot_password_error_no_configured_identity_server" = "Nessun identità server è configurata: aggiungine uno per reimpostare la password."; +"auth_forgot_password_error_no_configured_identity_server" = "Non è stato configurato alcun Identity: aggiungine uno per poter reimpostare la password."; "auth_softlogout_signed_out" = "Sei uscito"; "auth_softlogout_sign_in" = "Accedi"; -"auth_softlogout_reason" = "L'amministratore homeserver (%1$@) ti ha disconnesso dal tuo account %2$@ (%3$@)."; -"auth_softlogout_recover_encryption_keys" = "Accedi per recuperare le chiavi di crittografia archiviate esclusivamente su questo dispositivo. Ti servono per leggere tutti i tuoi messaggi sicuri su qualsiasi dispositivo."; -"auth_softlogout_clear_data" = "Cancella dati personali"; -"auth_softlogout_clear_data_message_1" = "Avviso: i tuoi dati personali (comprese le chiavi di crittografia) sono ancora memorizzati su questo dispositivo."; -"auth_softlogout_clear_data_message_2" = "Cancellalo se hai finito di utilizzare questo dispositivo o desideri accedere a un altro account."; +"auth_softlogout_reason" = "L'amministratore dell'Home Server (%1$@) ti ha disconnesso dal tuo account %2$@ (%3$@)."; +"auth_softlogout_recover_encryption_keys" = "Accedi per recuperare le chiavi crittografiche archiviate su questo dispositivo. Le chiavi ti servono per poter leggere i tuoi messaggi cifrati su altri dispositivi."; +"auth_softlogout_clear_data" = "Cancella i dati personali"; +"auth_softlogout_clear_data_message_1" = "Avviso: i tuoi dati personali (comprese le chiavi crittografiche) sono ancora memorizzati su questo dispositivo."; +"auth_softlogout_clear_data_message_2" = "Cancella se hai finito di utilizzare questo dispositivo o desideri accedere a un altro account."; "auth_softlogout_clear_data_button" = "Cancella tutti i dati"; "auth_softlogout_clear_data_sign_out_title" = "Sei sicuro?"; -"auth_softlogout_clear_data_sign_out_msg" = "Sei sicuro di voler cancellare tutti i dati attualmente memorizzati su questo dispositivo? Accedi di nuovo per accedere ai dati e ai messaggi del tuo account."; -"auth_softlogout_clear_data_sign_out" = "disconnessione"; -"room_creation_error_invite_user_by_email_without_identity_server" = "Nessuna identità server è configurata, quindi non è possibile aggiungere un partecipante con un'e-mail."; -"room_participants_start_new_chat_error_using_user_email_without_identity_server" = "Non è stato configurato alcun Identity Server, quindi non è possibile avviare una chat con un contatto tramite e-mail."; +"auth_softlogout_clear_data_sign_out_msg" = "Sei sicuro di voler cancellare tutti i dati memorizzati su questo dispositivo? Accedi di nuovo per gestire dati e messaggi del tuo account."; +"auth_softlogout_clear_data_sign_out" = "Disconnessione"; +"room_creation_error_invite_user_by_email_without_identity_server" = "Non è stato configuato nessun Identity Server e quindi non è possibile aggiungere partecipanti con un'e-mail."; +"room_participants_start_new_chat_error_using_user_email_without_identity_server" = "Non è stato configurato alcun Identity Server, quindi non è possibile avviare una chat con qualcuno tramite e-mail."; "room_event_action_reply" = "Rispondi"; "room_event_action_edit" = "Modifica"; "room_event_action_reaction_show_all" = "Mostra tutto"; @@ -681,7 +681,7 @@ "room_message_edits_history_title" = "Modifica messaggio"; "settings_labs_message_reaction" = "Reagisci ai messaggi con emoji"; "room_participants_remove_third_party_invite_prompt_msg" = "Sei sicuro di voler revocare l'invito?"; -"settings_key_backup_button_connect" = "Connetti questo dispositivo al Backup delle Chiavi"; +"settings_key_backup_button_connect" = "Connetti questo dispositivo al Backup delle chiavi"; // Media picker "media_picker_title" = "Media library"; // Image picker @@ -709,7 +709,7 @@ "device_verification_error_cannot_load_device" = "Non si riescono a recuperare le informazioni riguardanti il dipositivo."; // Mark: Incoming "device_verification_incoming_title" = "Richiesta di verifica in corso"; -"device_verification_incoming_description_1" = "Verifica questo dispositivo per contrassegnarlo come fidato. Verificare che i dispositivi dei tuoi contatti siano fidati dà maggiore tranquillità quando usi messaggi crittografati da-utente-a-utente con loro."; +"device_verification_incoming_description_1" = "Verifica questo dispositivo per contrassegnarlo come fidato. Verificare che i dispositivi dei tuoi contatti siano fidati offre maggior sicurezza quando scambi messaggi cifrati con loro."; "device_verification_incoming_description_2" = "Effettuando la verifica, il tuo dispositivo e quello del tuo contatto verranno reciprocamente contrassegnati come fidati."; // MARK: Start "device_verification_start_title" = "Verifica confrontando un breve testo"; @@ -724,7 +724,7 @@ // MARK: Verified "device_verification_verified_title" = "Verificato!"; "device_verification_verified_description_1" = "Hai verificato correttamente questo dispositivo."; -"device_verification_verified_description_2" = "Rendi sicuri i tuoi messaggi con questo contatto usando la crittografia da-utente-a-utente in modo che nessun'altro possa leggerli."; +"device_verification_verified_description_2" = "Cifra i messaggi che scambi con questo contatti in modo che nessun'altro possa leggerli."; "device_verification_verified_got_it_button" = "Fatto"; // MARK: Emoji "device_verification_emoji_dog" = "Cane"; @@ -807,8 +807,8 @@ // MARK: Reaction history "reaction_history_title" = "Reazioni"; // Errors -"error_user_already_logged_in" = "Sembra tu stia tentando di connetterti ad un altro Homeserver. Vuoi disconnetterti?"; -"room_accessiblity_scroll_to_bottom" = "Scorri in fondo"; +"error_user_already_logged_in" = "Sembra tu stia tentando di connetterti ad un altro Home Server. Vuoi disconnetterti?"; +"room_accessiblity_scroll_to_bottom" = "Scorri fino in fondo"; "room_accessibility_search" = "Cerca"; "room_accessibility_integrations" = "Integrazioni"; "room_accessibility_upload" = "Invia"; @@ -819,60 +819,60 @@ "media_type_accessibility_video" = "Video"; "media_type_accessibility_location" = "Posizione"; "media_type_accessibility_file" = "File"; -"media_type_accessibility_sticker" = "Adesivo"; +"media_type_accessibility_sticker" = "Sticker"; // Widget Picker "widget_picker_title" = "Integrazioni"; -"auth_add_email_message_2" = "Imposta un'email per il recupero dell'account, più tardi anche per essere trovabile dalle persone che ti conoscono."; -"auth_add_phone_message_2" = "Imposta un telefono, più tardi anche per essere trovabile dalle persone che ti conoscono."; -"auth_add_email_phone_message_2" = "Imposta un'email per il recupero dell'account. Più tardi usa l'email o il telefono per essere trovabile dalle persone che ti conoscono."; -"auth_email_is_required" = "Nessun server di identità configurato, perciò non puoi aggiungere un indirizzo email per ripristinare la password in futuro."; -"auth_phone_is_required" = "Nessun server di identità configurato, perciò non puoi aggiungere un numero di telefono per ripristinare la password in futuro."; -"auth_reset_password_error_is_required" = "Nessun server di identità configurato: aggiungine uno nelle opzioni server per ripristinare la password."; -"contacts_address_book_no_identity_server" = "Nessun server di identità configurato"; +"auth_add_email_message_2" = "Imposta un'email per il ripristino dell'account in caso di problemi e, se vuoi, anche per farti trovare da chi conosce quell'indirizzo email."; +"auth_add_phone_message_2" = "Aggiungi un numero di telefono se vuoi farti trovare da chi lo conosce."; +"auth_add_email_phone_message_2" = "Imposta un'email per il ripristino dell'account in caso di problemi. Email e telefono potranno essere usati anche per farti trovare dagli altri utenti."; +"auth_email_is_required" = "Non è stato configurato alcun Identity Server perciò non puoi aggiungere un indirizzo email utile a ripristinare la password in caso di problemi."; +"auth_phone_is_required" = "Non è stato configurato alcun Identity Server perciò non puoi aggiungere un numero di telefono utile a ripristinare la password in caso di problemi."; +"auth_reset_password_error_is_required" = "Non è stato configurato alcun Identity Server: aggiungine uno nelle opzioni server per poter ripristinare la password."; +"contacts_address_book_no_identity_server" = "Nessun Identity Server configurato"; "settings_discovery_settings" = "SCOPRI"; -"settings_identity_server_settings" = "SERVER IDENTITÀ"; -"settings_three_pids_management_information_part1" = "Gestisci qui quali indirizzi email o numeri di telefono puoi usare per accedere o recuperare l'account. Controlla chi può trovarti in "; +"settings_identity_server_settings" = "IDENTITY SERVER"; +"settings_three_pids_management_information_part1" = "Configura gli indirizzi email o numeri di telefono che puoi usare per accedere o per ripristinare l'account in caso di problemi. Controlla chi può trovarti in "; "settings_three_pids_management_information_part2" = "Scopri"; "settings_three_pids_management_information_part3" = "."; -"settings_calls_stun_server_fallback_button" = "Permetti server di assistenza alle chiamate di fallback"; -"settings_calls_stun_server_fallback_description" = "Consenti server di assistenza alle chiamate di fallback %@ quando il tuo homeserver non ne offre uno (il tuo indirizzo IP verrà condiviso durante una chiamata)."; -"settings_devices_description" = "Il nome pubblico di un dispositivo è visibile dalle persone con cui comunichi"; -"settings_discovery_no_identity_server" = "Attualmente non stai usando un server di identità. Per essere trovabile dai contatti esistenti che conosci, aggiungine uno."; -"settings_discovery_terms_not_signed" = "Accetta le condizioni di servizio del server di identità (%@) per poter essere trovabile tramite indirizzo email o numero di telefono."; -"settings_discovery_three_pids_management_information_part1" = "Gestisci quali indirizzi email o numeri di telefono gli altri utenti possono usare per trovarti e invitarti nelle stanze. Aggiungi o rimuovi indirizzi email o numeri di telefono da questa lista in "; +"settings_calls_stun_server_fallback_button" = "Permetti chiamate dal Server di appoggio"; +"settings_calls_stun_server_fallback_description" = "Se il tuo Home Server non ne ha un proprio Server d'appoggio verrà usato %s (il Server d'appoggio verrà a conoscenza del tuo indirizzo IP durante le chiamate)."; +"settings_devices_description" = "Il nome pubblico di un dispositivo è visibile alle persone con cui comunichi"; +"settings_discovery_no_identity_server" = "In questo momento non stai usando alcun Identity Server. Per trovare e farti trovare dagli altri utenti, configurane uno qua sotto."; +"settings_discovery_terms_not_signed" = "Accetta i termini di servizio dell'Identity Server (%@) per permettere ad altri utenti di trovarti tramite la tua email o numero di telefono."; +"settings_discovery_three_pids_management_information_part1" = "Configura gli indirizzi email o numeri di telefono con cui gli altri utenti potranno trovarti e invitarti nelle stanze. Aggiungi o rimuovi indirizzi email o numeri di telefono da questa lista in "; "settings_discovery_three_pids_management_information_part2" = "Impostazioni utente"; "settings_discovery_three_pids_management_information_part3" = "."; "settings_discovery_error_message" = "C'è stato un errore. Riprova."; "settings_discovery_three_pid_details_title_email" = "Gestisci email"; -"settings_discovery_three_pid_details_information_email" = "Gestisci le impostazioni di questo indirizzo email che altri utenti possono usare per trovarti o invitarti in altre stanze. Aggiungi o rimuovi gli indirizzi email in Accounts."; -"settings_discovery_three_pid_details_title_phone_number" = "Gestisci il numero di telefono"; -"settings_discovery_three_pid_details_information_phone_number" = "Gestisci le impostazioni di questo numero di telefono che altri utenti possono usare per trovarti o invitarti in altre stanze. Aggiungi o rimuovi i numeri di telefono in Accounts."; +"settings_discovery_three_pid_details_information_email" = "Configura le impostazioni di questo indirizzo email che altri utenti potranno usare per trovarti o invitarti in altre stanze. Aggiungi o rimuovi gli indirizzi email in Accounts."; +"settings_discovery_three_pid_details_title_phone_number" = "Configura il numero di telefono"; +"settings_discovery_three_pid_details_information_phone_number" = "Configura le impostazioni di questo numero di telefono che altri utenti possono usare per trovarti o invitarti in altre stanze. Aggiungi o rimuovi i numeri di telefono in Accounts."; "settings_discovery_three_pid_details_share_action" = "Condividi"; "settings_discovery_three_pid_details_revoke_action" = "Cancella"; "settings_discovery_three_pid_details_cancel_email_validation_action" = "Annulla la validazione dell'email"; "settings_discovery_three_pid_details_enter_sms_code_action" = "Inserisci il codice d'attivazione SMS"; "settings_identity_server_description" = "Utilizzando l'Identity Server impostato potrai trovare e farti trovare dai contatti esistenti."; -"settings_identity_server_no_is" = "Nessun Identity Server configurato"; +"settings_identity_server_no_is" = "Non è stato configurato alcun Identity Server"; "settings_identity_server_no_is_description" = "Non stai usando alcun Identity Server. Selezionane uno per trovare e farti trovare dai contatti esistenti."; // Identity server settings -"identity_server_settings_title" = "// Impostazioni dell'Identity Server"; +"identity_server_settings_title" = "Identity Server"; "identity_server_settings_description" = "Per trovare e farti trovare dai tuoi contatti stai usando %@."; "identity_server_settings_no_is_description" = "Non stai usando alcun Identity Server. Selezionane uno per trovare e farti trovare dai contatti esistenti."; "identity_server_settings_place_holder" = "Inserisci un Identity Server"; "identity_server_settings_add" = "Aggiungi"; "identity_server_settings_change" = "Cambia"; -"identity_server_settings_disconnect_info" = "Usare un Identity Server è facoltativo. Se scegli di non usarne uno gli altri utenti non potranno trovarti e tu non potrai invitarli per email o telefono."; +"identity_server_settings_disconnect_info" = "Se ti disconnetti dall'Identity Server gli altri utenti non potranno trovarti e tu non potrai invitarne di nuovi per email o telefono."; "identity_server_settings_disconnect" = "Disconnetti"; -"identity_server_settings_alert_no_terms_title" = "L'Identity Server non ha Termini di servizio"; -"identity_server_settings_alert_no_terms" = "L'Identity Server che hai scelto non ha Termini di servizio. Prosegui solo se ti fidi del gestore del server."; +"identity_server_settings_alert_no_terms_title" = "L'Identity Server non ha fornito dei termini di servizio"; +"identity_server_settings_alert_no_terms" = "L'Identity Server che hai scelto non ha freso noti i propri termini di servizio. Continua solo se ti fidi."; "identity_server_settings_alert_change_title" = "Cambia Identity Server"; -"identity_server_settings_alert_change" = "Vuoi scollegarti dall'Identyty Server %1$@ e connetterti invece a %2$@?"; -"identity_server_settings_alert_disconnect_title" = "Scollega dall'Identity Server"; +"identity_server_settings_alert_change" = "Vuoi scollegarti dall'Identity Server %1$@ e connetterti invece a %2$@?"; +"identity_server_settings_alert_disconnect_title" = "Scollega l'Identity Server"; "identity_server_settings_alert_disconnect" = "Vuoi scollegarti dall'Identity Server %@?"; "identity_server_settings_alert_disconnect_button" = "Scollega"; -"identity_server_settings_alert_disconnect_still_sharing_3pid" = "L'Identity Server %@ può ancora condividere le tue informazioni personali.\n\nTi raccomandiamo di rimuovere tutti i tuoi indirizzi email e numeri di telefono dall'Identity Server prima di scollegarti."; +"identity_server_settings_alert_disconnect_still_sharing_3pid" = "L'Identity Server %@ può ancora condividere le tue informazioni personali.\n\nTi raccomandiamo di rimuovere tutti i tuoi indirizzi email e numeri di telefono dall'Identity Server prima di scollegarlo."; "identity_server_settings_alert_disconnect_still_sharing_3pid_button" = "Scollegati comunque"; -"identity_server_settings_alert_error_terms_not_accepted" = "Devi accettare le condizioni di %@ per impostarlo come tuo Identity Server."; +"identity_server_settings_alert_error_terms_not_accepted" = "Devi accettare i termini di servizio di %@ per poterlo impostare come tuo Identity Server."; "identity_server_settings_alert_error_invalid_identity_server" = "%@ non é un Identity Server valido."; "call_no_stun_server_error_title" = "Chiamata fallita a causa di una configurazione errata del server"; "call_no_stun_server_error_message_1" = "Chiedi all'amministratore del tuo Homeserver %@ di configurare un server TURN in modo che le chiamate funzionino come si deve."; @@ -897,7 +897,7 @@ "widget_menu_remove" = "Rimuovi per tutti"; "settings_integrations" = "INTEGRAZIONI"; "settings_integrations_allow_button" = "Gestisci le integrazioni"; -"settings_integrations_allow_description" = "Usa un gestore di integrazioni (%@) per gestire bot, bridge, widget e pacchetti di adesivi.\n\nI gestori di integrazione ricevono dati di configurazione e possono modificare widget, inviare inviti alla stanza, assegnare permessi a tuo nome."; +"settings_integrations_allow_description" = "Usa un Integration Manager (%@) per gestire bot, bridge, widget e pacchetti di sticker.\n\nGli Integration Manager possono ricevere dati di configurazione, modificare widget, mandare inviti alle stanze e modificare permessi a tuo nome."; "widget_integration_manager_disabled" = "Devi attivare il gestore di integrazioni nelle impostazioni"; // Room widget permissions "room_widget_permission_title" = "Carica widget"; @@ -914,5 +914,5 @@ "accessibility_checkbox_label" = "checkbox"; "widget_picker_manage_integrations" = "Gestisci integrazioni..."; "service_terms_modal_policy_checkbox_accessibility_hint" = "Seleziona per accettare %@"; -"settings_labs_dm_key_verification" = "Verifica chiave via messaggio diretto"; +"settings_labs_dm_key_verification" = "Verifica chiave con messaggio diretto"; "settings_labs_cross_signing" = "Firma incrociata"; From 32f66cc7f05b674ddb6c2e242bed897656e827bd Mon Sep 17 00:00:00 2001 From: miyazakijunichi Date: Fri, 13 Dec 2019 01:04:56 +0000 Subject: [PATCH 031/282] Translated using Weblate (Japanese) Currently translated at 59.8% (508 of 850 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/ja/ --- Riot/Assets/ja.lproj/Vector.strings | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Riot/Assets/ja.lproj/Vector.strings b/Riot/Assets/ja.lproj/Vector.strings index f3312f9258..431ee92d63 100644 --- a/Riot/Assets/ja.lproj/Vector.strings +++ b/Riot/Assets/ja.lproj/Vector.strings @@ -562,3 +562,9 @@ "room_resource_limit_exceeded_message_contact_1" = " Please "; "settings_ui_theme_black" = "Black"; "settings_flair" = "特色を表示する"; +// String for App Store +"store_short_description" = "セキュアな分散型チャット/VoIP"; +"close" = "閉じる"; +// Accessibility +"accessibility_checkbox_label" = "チェックボックス"; +"auth_login_single_sign_on" = "シングルサインオン(SSO)でサインイン"; From 0e2aae1ac7acb02ba64483cbf540af6bd53e0166 Mon Sep 17 00:00:00 2001 From: Osoitz Date: Sun, 15 Dec 2019 15:35:18 +0000 Subject: [PATCH 032/282] Translated using Weblate (Basque) Currently translated at 100.0% (850 of 850 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/eu/ --- Riot/Assets/eu.lproj/Vector.strings | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Riot/Assets/eu.lproj/Vector.strings b/Riot/Assets/eu.lproj/Vector.strings index cea12ce87e..cd2c527e9a 100644 --- a/Riot/Assets/eu.lproj/Vector.strings +++ b/Riot/Assets/eu.lproj/Vector.strings @@ -924,3 +924,8 @@ "room_widget_permission_widget_id_permission" = "Trepetaren ID-a"; "room_widget_permission_room_id_permission" = "Gelaren ID-a"; "widget_picker_manage_integrations" = "Integrazioak kudeatu..."; +// Accessibility +"accessibility_checkbox_label" = "egiaztaketa-koadroa"; +"settings_labs_dm_key_verification" = "Gako egiaztaketa mezu zuzenaren bidez"; +"settings_labs_cross_signing" = "Zeharkako sinadura"; +"service_terms_modal_policy_checkbox_accessibility_hint" = "Markatu %@ onartzeko"; From 15d4231df6b014a7fcd63fb6af8774187b51ffc4 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 20 Dec 2019 10:30:59 +0100 Subject: [PATCH 033/282] Key verification: Add encryption images. --- .../Images.xcassets/Encryption/Contents.json | 6 +++++ .../encryption_normal.imageset/Contents.json | 23 ++++++++++++++++++ .../encryption_normal.png | Bin 0 -> 384 bytes .../encryption_normal@2x.png | Bin 0 -> 627 bytes .../encryption_normal@3x.png | Bin 0 -> 913 bytes .../encryption_trusted.imageset/Contents.json | 23 ++++++++++++++++++ .../encryption_trusted.png | Bin 0 -> 476 bytes .../encryption_trusted@2x.png | Bin 0 -> 804 bytes .../encryption_trusted@3x.png | Bin 0 -> 1156 bytes .../encryption_warning.imageset/Contents.json | 23 ++++++++++++++++++ .../encryption_warning.png | Bin 0 -> 423 bytes .../encryption_warning@2x.png | Bin 0 -> 688 bytes .../encryption_warning@3x.png | Bin 0 -> 980 bytes Riot/Generated/Images.swift | 3 +++ 14 files changed, 78 insertions(+) create mode 100644 Riot/Assets/Images.xcassets/Encryption/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal.png create mode 100644 Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal@2x.png create mode 100644 Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal@3x.png create mode 100644 Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/encryption_trusted.png create mode 100644 Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/encryption_trusted@2x.png create mode 100644 Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/encryption_trusted@3x.png create mode 100644 Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/encryption_warning.png create mode 100644 Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/encryption_warning@2x.png create mode 100644 Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/encryption_warning@3x.png diff --git a/Riot/Assets/Images.xcassets/Encryption/Contents.json b/Riot/Assets/Images.xcassets/Encryption/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Encryption/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/Contents.json b/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/Contents.json new file mode 100644 index 0000000000..91448ccb64 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "encryption_normal.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "encryption_normal@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "encryption_normal@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal.png b/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal.png new file mode 100644 index 0000000000000000000000000000000000000000..c628f322ab8e7df9d1f339202ed5a039f60e8fcc GIT binary patch literal 384 zcmV-`0e}99P)@rma;#oyE0Iw?~V`k~k!?LxdIbIh+j+s3%mx6(W4`mKlHs zT7?8XiPs6(nx=WT>HVNxod=9;>-Vk@XfRU7>=eM`*cyh>Xt$b}bEFxyqXr{fpLc+e zb}IET9uE3+trOg#1`XyP`3jcG^E_Fp*5v2ccgSL?EPda{N=F+nLXylCf?QXqzhF^Z euqpeHF%I@ch?(Ytvo%2g0000@y_6E6U`Y6quiW(Bp7ZN%d+&j$o+X3`z*I}gqzCMj$m^4RQ56^t+ANb^C`pBB zCM_#U$+00^TPnU*EGhql*c0q-_D`U<=sQKDQTDrKsJ*?7Mn;Co@Ap#&w-wz>KWmzn za0TGRlQL<*ejZ5;!(cyCGMOYd>AFtA;2;eJgACNCc1PHk402TrK&VN>;U~&wvs8`% z1Bg*;cJeR`pt%C{4+P3iT1-Blk6vFQ1prM>gS}4f7PE=|Sq*>&Km$MpNaKJN%xl?V zrriKyIAAUJTDGM)<_7S912z(`wY0d9YhIEI06Np49UmVfuY_2--hyVrz7rMza3`M6 z6j{Xcp1cc)fml)-YJb63zz`@N4zYWkpPf_$fosptPOY5~G1a@b@;O~7DSLU|$7!77Iv^hf-ozO0;`ZkHPwxcA;?8CAh>YbZtyC~n!2Gj} zb~yY6Vy<>>I*ttW3>>0LFfPg N002ovPDHLkV1lEn6D9xv literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal@3x.png b/Riot/Assets/Images.xcassets/Encryption/encryption_normal.imageset/encryption_normal@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..85e974954d12eefba707fde9226614a6d3feb9d0 GIT binary patch literal 913 zcmV;C18)3@P)wD+q-Vw#3 zo#H5QG_66<_5^)so?t;)A-Zja=a{3)af5UqL_@%7kjigIE$fJ52yKpCd?t-Ss5xLc z`HZKPCnv8LRsH(?KK(rTMUBR1YBmk~8Z0aWGI=u?AHyG-{61|80t|N17ecxlJmrUMIVT#+-iNNNyubw z(em=$VP*h~tjb1I2FiRW$?e_lZx-FK1y3s4514V%EazwD85^j28v+d=B@UZSPLD5a zpi8qPoxWllS}7E~W-4r;?Cp0k=tCgBR(nS}z{5k}1+ZRx?Az}G{o}+2bQ2rUO>96n zu>sx026XdZ8Zh0HWHL#*xtdOUF9#Mifd-cN=d5m|b-@q$?(P& z5#Y6eA@>zZX3k!@Mw%WFCcnrvm*agFgWh{6>?RKwZ){WpTYo)3>yvidnGsc4L?ve48z!a zWMV@CqmgpkApU**qjG{n_zHik7Yg_7a}OptRcC`{jpgi#?a5m%oleJkzEQHSUB77u z&O4p1YImk8(o!!hB5x`7$Y8`rczfI}rAT2>kF4O$KJ2MIrFllARar7}D%z%=E|G0? n1B?hCE^+LS@@kBUi3!givDIR|C@=Uh00000NkvXXu0mjfS}&gF literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/Contents.json b/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/Contents.json new file mode 100644 index 0000000000..cfcf9df660 --- /dev/null +++ b/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "encryption_trusted.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "encryption_trusted@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "encryption_trusted@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/encryption_trusted.png b/Riot/Assets/Images.xcassets/Encryption/encryption_trusted.imageset/encryption_trusted.png new file mode 100644 index 0000000000000000000000000000000000000000..748d42a77675cf24b1c85470e4acce44f02ae5d7 GIT binary patch literal 476 zcmV<20VDp2P)D#}xurJ&Ye~zW{Jl-j{r;Zj+yYDxAtXi(LF7Pt=+^sp-ylP^&>}khcR$A< zTtxJy`SV$9YR}NnIw0w)y{WL20zRGtA(@3(IMT*PHMGQWoWcSsQcpQLevv@7-T*^9 zJ1i`w;4o9bHC@GkN{AJto@#p%Ji3o51U`gq!)cy}vt_|m3;Tg@SGOTH8-caEx~uN$ zKuBak-#CE$Lfi$b1JknuGd({XcLOKD$2$m$p!Usw;nZA|F5S@%YJ4JNItAF3n^1q1 zJz*i4qqh>haIAo%+?OrwWc|MGISKYj`Dnr(;aGqZQo|6j^mvJHwZ>rj=Bt%03ZElf z1~1V=rFz0UuIA}s#9K_bittk$5tJ1;EN*$e&ZVu8UyR_Ntpluvl zIuw&Zp@Jr%f)$jWg0*1be4j7hC71h`YfMuLec;Vq?!NDRe&64GAv(^|bv=%tZc3V_ zICk`g${h2D0>epz(sbTps+WK7=;hs}N4hM7vSX;*D!<}p%9k;YBuA8^o~ZDn!cnbX zp-6v-l4l|m8&bu+MT;^DilS6J0oZs#n#Rq~VV2+Cr2Mx{s_ts!CABX|qoENIvB9wQ z^$JIZL9%WDy45tdRU&PFkJ=Gn;)5!UpN%xZFo5C-F!btK+oo;`p6aFVHy_ymdYlS( z+T1H=4;`^O4xsi8(Ce$?^eHhVy1lCaHS?sHaB3jPSG8rGjsY0v;XrN6nESjgmTgx6 ztmaQY7HImzlCQeH0I;d--Hdbuvfzl`jBe6NwCjXyf{%FcshXhxz;cw#}UY z#6GJAntwymJf4+5wJjL$-f)Z2D` z@0Wa;BbS^f74t74WIGSwAim>$&ne`4Pr^ zh9{kD`3lD!2G9-!U;;_;5VzjIF8LyQ+q{a9B;8v-~4sRaq|K i?#Kj!NYaUptNsB_h^_@ISi?*J0000B74<*4+OaKoi>-(m=P1(-uZnrJPq+jwfyVGg=ee>hJnKwjblol0KBfU7m2r{*(ytCv#q+9zw5{QHNZdkG3}MQJhdO}qoX%Cpo{*G%1YEuyr6 z#ui~)78ft3I6qBXO zRshJq{=Q1306GumJ9Cy^T@5MQoudC7WdWr|Nd*L|YiX@*kk;-DIt4f-N+tm0mv8k8 zZxg7gqv=2oc|l18z&(OIJdBAw7ZF^BN;+or*9WqC8U!smNI z-U6`GbORar_+c_H@_=UnAhgppLOb_IY$GLr&t>HA=1Cs#Ea2spPOq~w9@3;{FN4(DF zH|vJKEYX|TCn_W)E+r9h4E^Ca!lmZs2l(WV5G{3-W>=xrC8&#B}nT zb&H8jF&gp1J@Nu<#oyg{BqDbPG;&EE$mxd0m`=!>ru`~1cJNNf-sJ)uE79wgtwMzD zfhv(JgQqJdS^hN4+39f>;xroQ_32=@EW*Rvj8Ts8d`D))35l`jhnq{q%=%VR(-1OD}UKjcHk0Fo|AWaz_Rs z3gR)ZS4u?%lX|S%U-m)Gb2`Z|rq!55$gWXW@aa;ti*5iT!Ugu?^SQXjGRi2!J^lf` WdJ1Pue1g&d0000w3gFyfW6iKf^kW>&7f(L`)M-&tdpeP`@ljIOIVHZ2h z>~>j7KhkSWU-Ir_=gsWwzFlAdh$t^a7POOgnRWV7pJY*ImYH+^^?^&`SDCpEt&6yO z1RD21^TnzPOTfx9u*wX>8y>k}-UdOCxB?N2E4aD_-V#86+r$N)P-!6j@KY8RfsK+B zt#W}V_Zw)wTDoMx7B_Vqb@uFZZR9W}CZ|9PvoqaM*^e~s9=F<99^OW!W9%H{J9fMm zB>Ai+cFv29rUU+HVLGhhaGwKHUc9%>E>!%##Ed2s3zNS~kRqq-`=znXEl2EXJouR< z#v1XzZ-;WI=shJmIVV~xSsvAhlp=HBhJl=UW^4*QJ-fS5L{@Op=17TI@B=duqbk0L R0I>i7002ovPDHLkV1hxqrb+++ literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/encryption_warning@2x.png b/Riot/Assets/Images.xcassets/Encryption/encryption_warning.imageset/encryption_warning@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..15bd69270b338ecdee0bdd7bc1ae973c4cd1e16f GIT binary patch literal 688 zcmV;h0#E&kP)3NklqJNtG0Fq&y@y5T#6{Bwad8MTblQ9l|t7hyq+d1;SJy zXe=S<0GHSlxPf+=e|JxJx97XFEQ3JuFOBcDH}mb|X2-xomQpH5!(GY-Es0&KKxbkL zw*n(hpW2`cPEe)Jn_>;+=+d^$^W7}}UQ$pw?S<%TNKZjMyc5O% z$q?KL*(+M%OLB1fpfB<1 z_P#AZh_t4iCqbOngLA(!3X7w{cg@h(B=}#nQQMM?vSryeSdww2bJe>G(=Rey0M0pw z-SoUF_SsHv=Kvv)k{tf%3sy%2Nh&_LMgRK+{S_I_B=x*w01H7U6JT-LxG)e~)o7z= zui4v430^|4BoM=5tC5m?dRD3bMWs&8!e#k3+v2vw%Gv%Aam9Oi+?7Hgv@!JcP;~<* WG(<$;lpd-80000<2$D&FM6orW(n5ab4p$iM`z)}X1feoq2KY+r(0z;Ao39(Qln1bp~ zAV30v%Gj8VrbD}w^>|-`zW(-+M$Ul0}AMf)HYc4|my) z*>*FTj7`%HCY@v3V%rg3pzQ(r&@_Pw6%^^V2#&98HMUEX1QclyI5N`wz9-F$Hd`I{ zqzDMK288ryj8>7PU(aYeo}3Wv{~~Jb5Pkg2zyHYg>=~l`6{4$4d|Wa;%4W0Jwt3CG zrf~!)(ja_I9sNmv_(JraN$=YO(kA4Uki`X}xmmwnSo{_ngh0?S1reYYS)JsF`;%_% z@+f~I`tg&-0z}*>j1mJt$c9^pPC$hZTb|1g4tbOmei!~e7(7@u zJGhSqrzj50o+rAuE+5__06&ib@d#MFsA*fEI1rD32_hAcG*SUcBNdP|QUOWhzZ78i z&+tu?H1JhAfG4Oi3($Sj1cC0;J8BH!9K>(#zjXr8Nh+P=i{~#;Xdvjp=kq~FMsGMr zFccObKQsdz%vkO&o_GKu8%JdjQRp@xLPDS(79brpmgvD52Q+kD{1$Pwe}=#$iM4=d z-Yg8aG+T#%8-7q^1zKz@Zg^ z)Z(*d^rRP(zK)&XpiS!atT z4Iyh{4NuMGcSnfFPlZ@slZJD&c~MhtnJkz@QRr%9Agw3fj#^TR6-;Vfn2UX=sg-D& zq1>wC5^|*4{<*uvR_F#WBYZf=)(rD%Opzi*aQp=`a8$Ga!S&Ao0000 Date: Fri, 20 Dec 2019 10:32:09 +0100 Subject: [PATCH 034/282] UIStackView: Refactor extension method vc_removeAllSubviews to vc_removeAllArrangedSubviews. --- Riot/Categories/UIStackView.swift | 2 +- .../Room/ContextualMenu/ReactionsMenu/ReactionsMenuView.swift | 4 ++-- .../Room/ContextualMenu/RoomContextualMenuToolbarView.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Riot/Categories/UIStackView.swift b/Riot/Categories/UIStackView.swift index e1ebb800b0..b3040c9164 100644 --- a/Riot/Categories/UIStackView.swift +++ b/Riot/Categories/UIStackView.swift @@ -18,7 +18,7 @@ import UIKit extension UIStackView { - func vc_removeAllSubviews() { + func vc_removeAllArrangedSubviews() { let subviews = self.arrangedSubviews for subview in subviews { self.removeArrangedSubview(subview) diff --git a/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuView.swift b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuView.swift index 7913799cda..082e368e49 100644 --- a/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuView.swift +++ b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuView.swift @@ -103,13 +103,13 @@ final class ReactionsMenuView: UIView, Themable, NibLoadable { private func fill(reactionsMenuViewDatas: [ReactionMenuItemViewData]) { self.reactionViewDatas = reactionsMenuViewDatas - self.reactionsStackView.vc_removeAllSubviews() + self.reactionsStackView.vc_removeAllArrangedSubviews() let reactionsStackViewCount = self.reactionsStackView.arrangedSubviews.count // Remove all menu buttons if reactions count has changed if reactionsStackViewCount != self.reactionViewDatas.count { - self.reactionsStackView.vc_removeAllSubviews() + self.reactionsStackView.vc_removeAllArrangedSubviews() } var index = 0 diff --git a/Riot/Modules/Room/ContextualMenu/RoomContextualMenuToolbarView.swift b/Riot/Modules/Room/ContextualMenu/RoomContextualMenuToolbarView.swift index 8d695c9454..9ef0436e55 100644 --- a/Riot/Modules/Room/ContextualMenu/RoomContextualMenuToolbarView.swift +++ b/Riot/Modules/Room/ContextualMenu/RoomContextualMenuToolbarView.swift @@ -53,7 +53,7 @@ final class RoomContextualMenuToolbarView: MXKRoomInputToolbarView, NibOwnerLoad } @objc func fill(contextualMenuItems: [RoomContextualMenuItem]) { - self.menuItemsStackView.vc_removeAllSubviews() + self.menuItemsStackView.vc_removeAllArrangedSubviews() self.menuItemViews.removeAll() for menuItem in contextualMenuItems { From 25bb439aff3ad6461228f7077bc13d5e74a74113 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 20 Dec 2019 10:32:33 +0100 Subject: [PATCH 035/282] UIView: Add convenient vc_removeAllSubviews method. --- Riot/Categories/UIView.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Riot/Categories/UIView.swift b/Riot/Categories/UIView.swift index 5a46521041..e455766e14 100644 --- a/Riot/Categories/UIView.swift +++ b/Riot/Categories/UIView.swift @@ -19,7 +19,7 @@ import Foundation extension UIView { /// Add a subview matching parent view using autolayout - func vc_addSubViewMatchingParent(_ subView: UIView) { + @objc func vc_addSubViewMatchingParent(_ subView: UIView) { self.addSubview(subView) subView.translatesAutoresizingMaskIntoConstraints = false let views = ["view": subView] @@ -31,4 +31,10 @@ extension UIView { constraints.forEach { $0.isActive = true } } } + + @objc func vc_removeAllSubviews() { + for subView in self.subviews { + subView.removeFromSuperview() + } + } } From 4dcc6fb861d1473431911cef72537bb293430c15 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 20 Dec 2019 10:35:04 +0100 Subject: [PATCH 036/282] RoomBubbleCellData: Add new tags in RoomBubbleCellDataTag for key verification cells. --- .../Room/CellData/RoomBubbleCellData.h | 5 +- .../Room/CellData/RoomBubbleCellData.m | 54 +++++++++++++------ 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/Riot/Modules/Room/CellData/RoomBubbleCellData.h b/Riot/Modules/Room/CellData/RoomBubbleCellData.h index 7c34278ff2..7f355f5b13 100644 --- a/Riot/Modules/Room/CellData/RoomBubbleCellData.h +++ b/Riot/Modules/Room/CellData/RoomBubbleCellData.h @@ -21,7 +21,10 @@ typedef NS_ENUM(NSInteger, RoomBubbleCellDataTag) { RoomBubbleCellDataTagMessage = 0, // Default value used for messages RoomBubbleCellDataTagMembership, - RoomBubbleCellDataTagRoomCreateWithPredecessor + RoomBubbleCellDataTagRoomCreateWithPredecessor, + RoomBubbleCellDataTagDeviceKeyVerificationRequestIncomingApproval, + RoomBubbleCellDataTagDeviceKeyVerificationRequest, + RoomBubbleCellDataTagDeviceKeyVerificationConclusion }; /** diff --git a/Riot/Modules/Room/CellData/RoomBubbleCellData.m b/Riot/Modules/Room/CellData/RoomBubbleCellData.m index a89214ce48..c433215b84 100644 --- a/Riot/Modules/Room/CellData/RoomBubbleCellData.m +++ b/Riot/Modules/Room/CellData/RoomBubbleCellData.m @@ -62,26 +62,46 @@ - (instancetype)initWithEvent:(MXEvent *)event andRoomState:(MXRoomState *)roomS if (self) { - if (event.eventType == MXEventTypeRoomMember) + switch (event.eventType) { - // Membership events have their own cell type - self.tag = RoomBubbleCellDataTagMembership; - - // Membership events can be collapsed together - self.collapsable = YES; - - // Collapse them by default - self.collapsed = YES; - } - - if (event.eventType == MXEventTypeRoomCreate) - { - MXRoomCreateContent *createContent = [MXRoomCreateContent modelFromJSON:event.content]; - - if (createContent.roomPredecessorInfo) + case MXEventTypeRoomMember: + { + // Membership events have their own cell type + self.tag = RoomBubbleCellDataTagMembership; + + // Membership events can be collapsed together + self.collapsable = YES; + + // Collapse them by default + self.collapsed = YES; + } + break; + case MXEventTypeRoomCreate: { - self.tag = RoomBubbleCellDataTagRoomCreateWithPredecessor; + MXRoomCreateContent *createContent = [MXRoomCreateContent modelFromJSON:event.content]; + + if (createContent.roomPredecessorInfo) + { + self.tag = RoomBubbleCellDataTagRoomCreateWithPredecessor; + } } + break; + case MXEventTypeKeyVerificationCancel: + case MXEventTypeKeyVerificationDone: + self.tag = RoomBubbleCellDataTagDeviceKeyVerificationConclusion; + break; + case MXEventTypeRoomMessage: + { + NSString *msgType = event.content[@"msgtype"]; + + if ([msgType isEqualToString:kMXMessageTypeKeyVerificationRequest]) + { + self.tag = RoomBubbleCellDataTagDeviceKeyVerificationRequest; + } + } + break; + default: + break; } // Increase maximum number of components From fc77865f844adc55837f6365b1f3517f957cc696 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 20 Dec 2019 10:37:53 +0100 Subject: [PATCH 037/282] Add BubbleCellReadReceiptsDisplayable protocol describing a cell able to manage read receipts display. --- .../BubbleCellReadReceiptsDisplayable.swift | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellReadReceiptsDisplayable.swift diff --git a/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellReadReceiptsDisplayable.swift b/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellReadReceiptsDisplayable.swift new file mode 100644 index 0000000000..e9b657a03b --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellReadReceiptsDisplayable.swift @@ -0,0 +1,22 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +@objc protocol BubbleCellReadReceiptsDisplayable { + func addReadReceiptsView(_ readReceiptsView: UIView) + func removeReadReceiptsView() +} From d4fd68e40551fae87a41b1de7341ed353c4d6ce8 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 20 Dec 2019 10:39:22 +0100 Subject: [PATCH 038/282] Create BubbleCellWithoutSenderInfoContentView a base room bubble cell content view. --- ...bbleCellWithoutSenderInfoContentView.swift | 74 +++++++++++++++ ...BubbleCellWithoutSenderInfoContentView.xib | 93 +++++++++++++++++++ 2 files changed, 167 insertions(+) create mode 100644 Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellWithoutSenderInfoContentView.swift create mode 100644 Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellWithoutSenderInfoContentView.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellWithoutSenderInfoContentView.swift b/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellWithoutSenderInfoContentView.swift new file mode 100644 index 0000000000..e4c7a9bd87 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellWithoutSenderInfoContentView.swift @@ -0,0 +1,74 @@ +/* + Copyright 2019 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 + +@objcMembers +final class BubbleCellWithoutSenderInfoContentView: UIView, NibLoadable { + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet weak var bubbleInfoContainer: UIView! + @IBOutlet weak var bubbleInfoContainerTopConstraint: NSLayoutConstraint! + + @IBOutlet weak var innerContentView: UIView! + + @IBOutlet weak var readReceiptsContainerView: UIView! + @IBOutlet weak var readReceiptsContentView: UIView! + + @IBOutlet weak var bubbleOverlayContainer: UIView! + + // MARK: Private + + private var showReadReceipts: Bool { + get { + return self.readReceiptsContainerView.isHidden + } + set { + self.readReceiptsContainerView.isHidden = !newValue + } + } + + // MARK: - Setup + + class func instantiate() -> BubbleCellWithoutSenderInfoContentView { + return BubbleCellWithoutSenderInfoContentView.loadFromNib() + } + + // MARK: - Public + + func update(theme: Theme) { + self.backgroundColor = theme.backgroundColor + } +} + +// MARK: - BubbleCellReadReceiptsDisplayable +extension BubbleCellWithoutSenderInfoContentView: BubbleCellReadReceiptsDisplayable { + + func addReadReceiptsView(_ readReceiptsView: UIView) { + self.readReceiptsContentView.vc_removeAllSubviews() + self.readReceiptsContentView.vc_addSubViewMatchingParent(readReceiptsView) + self.showReadReceipts = true + } + + func removeReadReceiptsView() { + self.showReadReceipts = false + self.readReceiptsContentView.vc_removeAllSubviews() + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellWithoutSenderInfoContentView.xib b/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellWithoutSenderInfoContentView.xib new file mode 100644 index 0000000000..ac074f0caf --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellWithoutSenderInfoContentView.xib @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 48db9c37a4f470d8748c74864bd0a9d0f0b0b9a2 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 20 Dec 2019 10:42:28 +0100 Subject: [PATCH 039/282] Create DM key verification cells. --- .../KeyVerificationBaseBubbleCell.swift | 223 ++++++++++++++++++ .../KeyVerificationCellInnerContentView.swift | 159 +++++++++++++ .../KeyVerificationCellInnerContentView.xib | 99 ++++++++ .../KeyVerificationConclusionBubbleCell.swift | 103 ++++++++ .../KeyVerificationConclusionViewData.swift | 24 ++ ...ionIncomingRequestApprovalBubbleCell.swift | 99 ++++++++ ...ationIncomingRequestApprovalViewData.swift | 23 ++ ...yVerificationRequestStatusBubbleCell.swift | 91 +++++++ ...KeyVerificationRequestStatusViewData.swift | 24 ++ .../KeyVerification/SizingViewHeight.swift | 43 ++++ 10 files changed, 888 insertions(+) create mode 100644 Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationBaseBubbleCell.swift create mode 100644 Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationCellInnerContentView.swift create mode 100644 Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationCellInnerContentView.xib create mode 100644 Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionBubbleCell.swift create mode 100644 Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionViewData.swift create mode 100644 Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationIncomingRequestApprovalBubbleCell.swift create mode 100644 Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationIncomingRequestApprovalViewData.swift create mode 100644 Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationRequestStatusBubbleCell.swift create mode 100644 Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationRequestStatusViewData.swift create mode 100644 Riot/Modules/Room/Views/BubbleCells/KeyVerification/SizingViewHeight.swift diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationBaseBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationBaseBubbleCell.swift new file mode 100644 index 0000000000..a280a979e2 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationBaseBubbleCell.swift @@ -0,0 +1,223 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +@objcMembers +class KeyVerificationBaseBubbleCell: MXKRoomBubbleTableViewCell { + + // MARK: - Constants + + private enum Sizing { + static var sizes = Set() + } + + // MARK: - Properties + + // MARK: Public + + weak var keyVerificationCellInnerContentView: KeyVerificationCellInnerContentView? + weak var bubbleCellWithoutSenderInfoContentView: BubbleCellWithoutSenderInfoContentView? + + override var bubbleInfoContainer: UIView! { + get { + guard let infoContainer = self.bubbleCellWithoutSenderInfoContentView?.bubbleInfoContainer else { + fatalError("[KeyVerificationBaseBubbleCell] bubbleInfoContainer should not be used before set") + } + return infoContainer + } + set { + super.bubbleInfoContainer = newValue + } + } + + override var bubbleOverlayContainer: UIView! { + get { + guard let overlayContainer = self.bubbleCellWithoutSenderInfoContentView?.bubbleOverlayContainer else { + fatalError("[KeyVerificationBaseBubbleCell] bubbleOverlayContainer should not be used before set") + } + return overlayContainer + } + set { + super.bubbleInfoContainer = newValue + } + } + + override var bubbleInfoContainerTopConstraint: NSLayoutConstraint! { + get { + guard let infoContainerTopConstraint = self.bubbleCellWithoutSenderInfoContentView?.bubbleInfoContainerTopConstraint else { + fatalError("[KeyVerificationBaseBubbleCell] bubbleInfoContainerTopConstraint should not be used before set") + } + return infoContainerTopConstraint + } + set { + super.bubbleInfoContainerTopConstraint = newValue + } + } + + // MARK: - Setup + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + self.commonInit() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func commonInit() { + + self.selectionStyle = .none + self.setupContentView() + self.update(theme: ThemeService.shared().theme) + + super.setupViews() + } + + // MARK: - Public + + func update(theme: Theme) { + self.bubbleCellWithoutSenderInfoContentView?.update(theme: theme) + self.keyVerificationCellInnerContentView?.update(theme: theme) + } + + func buildUserInfoText(with userId: String, userDisplayName: String?) -> String { + + let userInfoText: String + + if let userDisplayName = userDisplayName { + userInfoText = "\(userId) (\(userDisplayName))" + } else { + userInfoText = userId + } + + return userInfoText + } + + func senderId(from bubbleCellData: MXKRoomBubbleCellData) -> String { + return bubbleCellData.senderId ?? "" + } + + func senderDisplayName(from bubbleCellData: MXKRoomBubbleCellData) -> String? { + let senderId = self.senderId(from: bubbleCellData) + guard let senderDisplayName = bubbleCellData.senderDisplayName, senderId != senderDisplayName else { + return nil + } + return senderDisplayName + } + + class func sizingView() -> MXKRoomBubbleTableViewCell { + fatalError("[KeyVerificationBaseBubbleCell] Subclass should implement this method") + } + + // TODO: Implement thiscmethod in subclasses + class func sizingHeightHashValue(from bubbleData: MXKRoomBubbleCellData) -> Int { + return bubbleData.hashValue + } + + // MARK: - Overrides + + override class func defaultReuseIdentifier() -> String! { + return String(describing: self) + } + + override func didEndDisplay() { + super.didEndDisplay() + self.removeReadReceiptsView() + } + + override class func height(for cellData: MXKCellData!, withMaximumWidth maxWidth: CGFloat) -> CGFloat { + guard let cellData = cellData else { + return 0 + } + + guard let roomBubbleCellData = cellData as? MXKRoomBubbleCellData else { + return 0 + } + + let height: CGFloat + + let sizingViewHeight = self.findOrCreateSizingViewHeight(from: roomBubbleCellData) + + if let cachedHeight = sizingViewHeight.heights[maxWidth] { + height = cachedHeight + } else { + height = self.contentViewHeight(for: roomBubbleCellData, fitting: maxWidth) + sizingViewHeight.heights[maxWidth] = height + } + + return height + } + + // MARK: - Private + + private func setupContentView() { + if self.bubbleCellWithoutSenderInfoContentView == nil { + + let bubbleCellWithoutSenderInfoContentView = BubbleCellWithoutSenderInfoContentView.instantiate() + + let innerContentView = KeyVerificationCellInnerContentView.instantiate() + + bubbleCellWithoutSenderInfoContentView.innerContentView.vc_addSubViewMatchingParent(innerContentView) + + self.contentView.vc_addSubViewMatchingParent(bubbleCellWithoutSenderInfoContentView) + + self.bubbleCellWithoutSenderInfoContentView = bubbleCellWithoutSenderInfoContentView + self.keyVerificationCellInnerContentView = innerContentView + } + } + + private static func findOrCreateSizingViewHeight(from bubbleData: MXKRoomBubbleCellData) -> SizingViewHeight { + + let sizingViewHeight: SizingViewHeight + let bubbleDataHashValue = bubbleData.hashValue + + if let foundSizingViewHeight = self.Sizing.sizes.first(where: { (sizingViewHeight) -> Bool in + return sizingViewHeight.uniqueIdentifier == bubbleDataHashValue + }) { + sizingViewHeight = foundSizingViewHeight + } else { + sizingViewHeight = SizingViewHeight(uniqueIdentifier: bubbleDataHashValue) + } + + return sizingViewHeight + } + + private static func contentViewHeight(for cellData: MXKCellData, fitting width: CGFloat) -> CGFloat { + let sizingView = self.sizingView() + + sizingView.render(cellData) + sizingView.layoutIfNeeded() + + let fittingSize = CGSize(width: width, height: UIView.layoutFittingCompressedSize.height) + let height = sizingView.systemLayoutSizeFitting(fittingSize).height + + return height + } +} + +// MARK: - BubbleCellReadReceiptsDisplayable +extension KeyVerificationBaseBubbleCell: BubbleCellReadReceiptsDisplayable { + + func addReadReceiptsView(_ readReceiptsView: UIView) { + self.bubbleCellWithoutSenderInfoContentView?.addReadReceiptsView(readReceiptsView) + } + + func removeReadReceiptsView() { + self.bubbleCellWithoutSenderInfoContentView?.removeReadReceiptsView() + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationCellInnerContentView.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationCellInnerContentView.swift new file mode 100644 index 0000000000..4b859339f1 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationCellInnerContentView.swift @@ -0,0 +1,159 @@ +/* + Copyright 2019 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 + +final class KeyVerificationCellInnerContentView: UIView, NibLoadable { + + // MARK: - Constants + + private enum Constants { + static let cornerRadius: CGFloat = 8.0 + static let buttonBackgroundColorAlpha: CGFloat = 0.8 + static let buttonCornerRadius: CGFloat = 6.0 + } + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var badgeImageView: UIImageView! + @IBOutlet private weak var titleLabel: UILabel! + + @IBOutlet private weak var userInformationsLabel: UILabel! + + @IBOutlet private weak var requestStatusLabel: UILabel! + + @IBOutlet private weak var acceptButton: UIButton! + @IBOutlet private weak var declineButton: UIButton! + + // MARK: Public + + var isButtonsHidden: Bool { + get { + return self.acceptButton.isHidden && self.declineButton.isHidden + } + set { + self.acceptButton.isHidden = newValue + self.declineButton.isHidden = newValue + } + } + + var isRequestStatusHidden: Bool { + get { + return self.requestStatusLabel.isHidden + } + set { + self.requestStatusLabel.isHidden = newValue + } + } + + var badgeImage: UIImage? { + get { + return self.badgeImageView.image + } + set { + self.badgeImageView.image = newValue + } + } + + var title: String? { + get { + return self.titleLabel.text + } + set { + self.titleLabel.text = newValue + } + } + + var requestStatusText: String? { + get { + return self.requestStatusLabel.text + } + set { + self.requestStatusLabel.text = newValue + } + } + + var acceptActionHandler: (() -> Void)? + + var declineActionHandler: (() -> Void)? + + // MARK: - Setup + + static func instantiate() -> KeyVerificationCellInnerContentView { + let view = KeyVerificationCellInnerContentView.loadFromNib() + return view + } + + // MARK: - Life cycle + + override func awakeFromNib() { + super.awakeFromNib() + + self.layer.masksToBounds = true + } + + override func layoutSubviews() { + super.layoutSubviews() + + self.layer.cornerRadius = Constants.cornerRadius + + if self.isButtonsHidden == false { + self.acceptButton.layer.cornerRadius = Constants.buttonCornerRadius + self.declineButton.layer.cornerRadius = Constants.buttonCornerRadius + } + } + + // MARK: - Public + + func update(theme: Theme) { + self.backgroundColor = theme.headerBackgroundColor + self.titleLabel.textColor = theme.textPrimaryColor + self.userInformationsLabel.textColor = theme.textSecondaryColor + + self.acceptButton.vc_setBackgroundColor(theme.tintColor.withAlphaComponent(Constants.buttonBackgroundColorAlpha), for: .normal) + self.declineButton.vc_setBackgroundColor(theme.noticeColor.withAlphaComponent(Constants.buttonBackgroundColorAlpha), for: .normal) + } + + func updateSenderInfo(with userId: String, userDisplayName: String?) { + self.userInformationsLabel.text = self.buildUserInfoText(with: userId, userDisplayName: userDisplayName) + } + + // MARK: - Private + + private func buildUserInfoText(with userId: String, userDisplayName: String?) -> String { + + let userInfoText: String + + if let userDisplayName = userDisplayName { + userInfoText = "\(userId) (\(userDisplayName))" + } else { + userInfoText = userId + } + + return userInfoText + } + + @IBAction private func declineButtonAction(_ sender: Any) { + self.declineActionHandler?() + } + + @IBAction private func acceptButtonAction(_ sender: Any) { + self.acceptActionHandler?() + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationCellInnerContentView.xib b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationCellInnerContentView.xib new file mode 100644 index 0000000000..ca7a98a8c1 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationCellInnerContentView.xib @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionBubbleCell.swift new file mode 100644 index 0000000000..6bf6b0a5d4 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionBubbleCell.swift @@ -0,0 +1,103 @@ +/* + Copyright 2019 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 + +@objcMembers +final class KeyVerificationConclusionBubbleCell: KeyVerificationBaseBubbleCell { + + // MARK: - Constants + + private enum Sizing { + static let view = KeyVerificationConclusionBubbleCell(style: .default, reuseIdentifier: nil) + } + + // MARK: - Setup + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + self.commonInit() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func commonInit() { + self.keyVerificationCellInnerContentView?.isButtonsHidden = true + self.keyVerificationCellInnerContentView?.isRequestStatusHidden = true + } + + // MARK: - Overrides + + override func render(_ cellData: MXKCellData!) { + super.render(cellData) + + guard let keyVerificationCellInnerContentView = self.keyVerificationCellInnerContentView, + let bubbleData = self.bubbleData, + let viewData = self.viewData(from: bubbleData) else { + NSLog("[KeyVerificationConclusionBubbleCell] Fail to render \(String(describing: cellData))") + return + } + + keyVerificationCellInnerContentView.badgeImage = viewData.badgeImage + keyVerificationCellInnerContentView.title = viewData.title + keyVerificationCellInnerContentView.updateSenderInfo(with: viewData.senderId, userDisplayName: viewData.senderDisplayName) + } + + override class func sizingView() -> MXKRoomBubbleTableViewCell { + return self.Sizing.view + } + + // MARK: - Private + + // TODO: Handle view data filling + private func viewData(from bubbleData: MXKRoomBubbleCellData) -> KeyVerificationConclusionViewData? { + guard let event = bubbleData.bubbleComponents.first?.event else { + return nil + } + + let viewData: KeyVerificationConclusionViewData? + + let senderId = self.senderId(from: bubbleData) + let senderDisplayName = self.senderDisplayName(from: bubbleData) + let title: String? + let badgeImage: UIImage? + + switch event.eventType { + case .keyVerificationDone: + title = "Verified" + badgeImage = Asset.Images.encryptionTrusted.image + case .keyVerificationCancel: + title = "Cancelled" + badgeImage = Asset.Images.encryptionNormal.image + default: + badgeImage = nil + title = nil + } + + if let title = title, let badgeImage = badgeImage { + viewData = KeyVerificationConclusionViewData(badgeImage: badgeImage, + title: title, + senderId: senderId, + senderDisplayName: senderDisplayName) + } else { + viewData = nil + } + + return viewData + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionViewData.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionViewData.swift new file mode 100644 index 0000000000..4df053de6c --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionViewData.swift @@ -0,0 +1,24 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +struct KeyVerificationConclusionViewData { + let badgeImage: UIImage + let title: String + let senderId: String + let senderDisplayName: String? +} diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationIncomingRequestApprovalBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationIncomingRequestApprovalBubbleCell.swift new file mode 100644 index 0000000000..9cc1c01363 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationIncomingRequestApprovalBubbleCell.swift @@ -0,0 +1,99 @@ +/* + Copyright 2019 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 + +@objcMembers +final class KeyVerificationIncomingRequestApprovalBubbleCell: KeyVerificationBaseBubbleCell { + + // MARK: - Constants + + private enum Sizing { + static let view = KeyVerificationConclusionBubbleCell(style: .default, reuseIdentifier: nil) + } + + // MARK: - Setup + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + self.commonInit() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func commonInit() { + guard let keyVerificationCellInnerContentView = self.keyVerificationCellInnerContentView else { + fatalError("[KeyVerificationIncomingRequestApprovalBubbleCell] keyVerificationCellInnerContentView should not be nil") + } + + keyVerificationCellInnerContentView.isButtonsHidden = false + keyVerificationCellInnerContentView.isRequestStatusHidden = true + keyVerificationCellInnerContentView.badgeImage = Asset.Images.encryptionNormal.image + } + + // MARK: - Overrides + + override func prepareForReuse() { + super.prepareForReuse() + + self.keyVerificationCellInnerContentView?.acceptActionHandler = nil + self.keyVerificationCellInnerContentView?.declineActionHandler = nil + } + + override func render(_ cellData: MXKCellData!) { + super.render(cellData) + + guard let keyVerificationCellInnerContentView = self.keyVerificationCellInnerContentView, + let bubbleData = self.bubbleData, + let viewData = self.viewData(from: bubbleData) else { + NSLog("[KeyVerificationIncomingRequestApprovalBubbleCell] Fail to render \(String(describing: cellData))") + return + } + + keyVerificationCellInnerContentView.title = viewData.title + keyVerificationCellInnerContentView.updateSenderInfo(with: viewData.senderId, userDisplayName: viewData.senderDisplayName) + + keyVerificationCellInnerContentView.acceptActionHandler = { [weak self] in + // TODO: Use correct action identifier + self?.delegate?.cell(self, didRecognizeAction: kMXKRoomBubbleCellTapOnContentView, userInfo: nil) + } + + keyVerificationCellInnerContentView.declineActionHandler = { [weak self] in + // TODO: Use correct action identifier + self?.delegate?.cell(self, didRecognizeAction: kMXKRoomBubbleCellTapOnContentView, userInfo: nil) + } + } + + override class func sizingView() -> MXKRoomBubbleTableViewCell { + return self.Sizing.view + } + + // MARK: - Private + + // TODO: Handle view data filling + private func viewData(from bubbleData: MXKRoomBubbleCellData) -> KeyVerificationIncomingRequestApprovalViewData? { + + let senderId = self.senderId(from: bubbleData) + let senderDisplayName = self.senderDisplayName(from: bubbleData) + let title = "Verification request" + + return KeyVerificationIncomingRequestApprovalViewData(title: title, + senderId: senderId, + senderDisplayName: senderDisplayName) + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationIncomingRequestApprovalViewData.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationIncomingRequestApprovalViewData.swift new file mode 100644 index 0000000000..91824fe16c --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationIncomingRequestApprovalViewData.swift @@ -0,0 +1,23 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +struct KeyVerificationIncomingRequestApprovalViewData { + let title: String + let senderId: String + let senderDisplayName: String? +} diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationRequestStatusBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationRequestStatusBubbleCell.swift new file mode 100644 index 0000000000..453407ced1 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationRequestStatusBubbleCell.swift @@ -0,0 +1,91 @@ +/* + Copyright 2019 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 + +@objcMembers +final class KeyVerificationRequestStatusBubbleCell: KeyVerificationBaseBubbleCell { + + // MARK: - Constants + + private enum Sizing { + static let view = KeyVerificationRequestStatusBubbleCell(style: .default, reuseIdentifier: nil) + } + + // MARK: - Setup + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + self.commonInit() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func commonInit() { + guard let keyVerificationCellInnerContentView = self.keyVerificationCellInnerContentView else { + fatalError("[KeyVerificationRequestStatusBubbleCell] keyVerificationCellInnerContentView should not be nil") + } + + keyVerificationCellInnerContentView.isButtonsHidden = true + keyVerificationCellInnerContentView.isRequestStatusHidden = false + keyVerificationCellInnerContentView.badgeImage = Asset.Images.encryptionNormal.image + } + + // MARK: - Overrides + + override func render(_ cellData: MXKCellData!) { + super.render(cellData) + + guard let keyVerificationCellInnerContentView = self.keyVerificationCellInnerContentView, + let bubbleData = self.bubbleData, + let viewData = self.viewData(from: bubbleData) else { + NSLog("[KeyVerificationRequestStatusBubbleCell] Fail to render \(String(describing: cellData))") + return + } + + keyVerificationCellInnerContentView.title = viewData.title + keyVerificationCellInnerContentView.updateSenderInfo(with: viewData.senderId, userDisplayName: viewData.senderDisplayName) + keyVerificationCellInnerContentView.requestStatusText = viewData.statusText + } + + override class func sizingView() -> MXKRoomBubbleTableViewCell { + return self.Sizing.view + } + + // MARK: - Private + + // TODO: Handle view data filling + private func viewData(from bubbleData: MXKRoomBubbleCellData) -> KeyVerificationRequestStatusViewData? { + + let senderId = self.senderId(from: bubbleData) + let senderDisplayName = self.senderDisplayName(from: bubbleData) + let title: String + let statusText: String = "You accepted" + + if senderId.isEmpty == false { + title = "Verification request" + } else { + title = "Verification sent" + } + + return KeyVerificationRequestStatusViewData(title: title, + senderId: senderId, + senderDisplayName: senderDisplayName, + statusText: statusText) + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationRequestStatusViewData.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationRequestStatusViewData.swift new file mode 100644 index 0000000000..c411b56ca5 --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationRequestStatusViewData.swift @@ -0,0 +1,24 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +struct KeyVerificationRequestStatusViewData { + let title: String + let senderId: String + let senderDisplayName: String? + let statusText: String +} diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/SizingViewHeight.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/SizingViewHeight.swift new file mode 100644 index 0000000000..f1607fb4fb --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/SizingViewHeight.swift @@ -0,0 +1,43 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +final class SizingViewHeight: Hashable, Equatable { + + // MARK: - Properties + + let uniqueIdentifier: Int + var heights: [CGFloat /* width */: CGFloat /* height */] = [:] + + // MARK: - Setup + + init(uniqueIdentifier: Int) { + self.uniqueIdentifier = uniqueIdentifier + } + + // MARK: - Hashable + + func hash(into hasher: inout Hasher) { + hasher.combine(self.uniqueIdentifier) + } + + // MARK: - Equatable + + static func == (lhs: SizingViewHeight, rhs: SizingViewHeight) -> Bool { + return lhs.uniqueIdentifier == rhs.uniqueIdentifier + } +} From 2fba33e598d78f6110c3c9f43f6ca8a67ed5409d Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 20 Dec 2019 10:43:07 +0100 Subject: [PATCH 040/282] RoomVC: Handle DM key verification cells. --- Riot/Modules/Room/RoomViewController.m | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 2c4fac7951..03ccc1656c 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -353,6 +353,10 @@ - (void)viewDidLoad [self.bubblesTableView registerClass:RoomSelectedStickerBubbleCell.class forCellReuseIdentifier:RoomSelectedStickerBubbleCell.defaultReuseIdentifier]; [self.bubblesTableView registerClass:RoomPredecessorBubbleCell.class forCellReuseIdentifier:RoomPredecessorBubbleCell.defaultReuseIdentifier]; + [self.bubblesTableView registerClass:KeyVerificationIncomingRequestApprovalBubbleCell.class forCellReuseIdentifier:KeyVerificationIncomingRequestApprovalBubbleCell.defaultReuseIdentifier]; + [self.bubblesTableView registerClass:KeyVerificationRequestStatusBubbleCell.class forCellReuseIdentifier:KeyVerificationRequestStatusBubbleCell.defaultReuseIdentifier]; + [self.bubblesTableView registerClass:KeyVerificationConclusionBubbleCell.class forCellReuseIdentifier:KeyVerificationConclusionBubbleCell.defaultReuseIdentifier]; + // Prepare expanded header expandedHeader = [ExpandedRoomTitleView roomTitleView]; expandedHeader.delegate = self; @@ -2039,6 +2043,18 @@ - (void)displayRoomPreview:(RoomPreviewData *)previewData { cellViewClass = RoomPredecessorBubbleCell.class; } + else if (bubbleData.tag == RoomBubbleCellDataTagDeviceKeyVerificationRequestIncomingApproval) + { + cellViewClass = KeyVerificationIncomingRequestApprovalBubbleCell.class; + } + else if (bubbleData.tag == RoomBubbleCellDataTagDeviceKeyVerificationRequest) + { + cellViewClass = KeyVerificationRequestStatusBubbleCell.class; + } + else if (bubbleData.tag == RoomBubbleCellDataTagDeviceKeyVerificationConclusion) + { + cellViewClass = KeyVerificationConclusionBubbleCell.class; + } else if (bubbleData.tag == RoomBubbleCellDataTagMembership) { if (bubbleData.collapsed) From 5cfe5833ce2b3a8cd33ffc73ab2af1add3a13050 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 20 Dec 2019 10:44:01 +0100 Subject: [PATCH 041/282] RoomDataSource: Handle read receipts display for cells conforming to BubbleCellReadReceiptsDisplayable. --- .../Modules/Room/DataSources/RoomDataSource.m | 82 +++++++++++-------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m index 103fd0accf..5b82ce4fee 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -284,7 +284,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N ]]; } - MXKReceiptSendersContainer* avatarsContainer; + MXKReceiptSendersContainer* avatarsContainer; // Handle read receipts (if any) if (self.showBubbleReceipts && cellData.readReceipts.count && !isCollapsableCellCollapsed) @@ -349,47 +349,57 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N { [bubbleCell.tmpSubviews addObject:avatarsContainer]; } - [bubbleCell.contentView addSubview:avatarsContainer]; - // Force receipts container size - NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:avatarsContainer - attribute:NSLayoutAttributeWidth - relatedBy:NSLayoutRelationEqual - toItem:nil - attribute:NSLayoutAttributeNotAnAttribute - multiplier:1.0 - constant:RoomBubbleCellLayout.readReceiptsViewWidth]; - NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:avatarsContainer - attribute:NSLayoutAttributeHeight - relatedBy:NSLayoutRelationEqual - toItem:nil - attribute:NSLayoutAttributeNotAnAttribute - multiplier:1.0 - constant:RoomBubbleCellLayout.readReceiptsViewHeight]; - - // Force receipts container position - NSLayoutConstraint *trailingConstraint = [NSLayoutConstraint constraintWithItem:avatarsContainer - attribute:NSLayoutAttributeTrailing - relatedBy:NSLayoutRelationEqual - toItem:avatarsContainer.superview - attribute:NSLayoutAttributeTrailing - multiplier:1.0 - constant:-RoomBubbleCellLayout.readReceiptsViewRightMargin]; - - // At the bottom, we have reactions or nothing - NSLayoutConstraint *topConstraint; - if (reactionsView) + if ([[bubbleCell class] conformsToProtocol:@protocol(BubbleCellReadReceiptsDisplayable)]) { - topConstraint = [avatarsContainer.topAnchor constraintEqualToAnchor:reactionsView.bottomAnchor constant:RoomBubbleCellLayout.readReceiptsViewTopMargin]; + id readReceiptsDisplayable = (id)bubbleCell; + + [readReceiptsDisplayable addReadReceiptsView:avatarsContainer]; } else { - topConstraint = [avatarsContainer.topAnchor constraintEqualToAnchor:avatarsContainer.superview.topAnchor constant:bottomPositionY + RoomBubbleCellLayout.readReceiptsViewTopMargin]; + [bubbleCell.contentView addSubview:avatarsContainer]; + + // Force receipts container size + NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:avatarsContainer + attribute:NSLayoutAttributeWidth + relatedBy:NSLayoutRelationEqual + toItem:nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1.0 + constant:RoomBubbleCellLayout.readReceiptsViewWidth]; + NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:avatarsContainer + attribute:NSLayoutAttributeHeight + relatedBy:NSLayoutRelationEqual + toItem:nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1.0 + constant:RoomBubbleCellLayout.readReceiptsViewHeight]; + + // Force receipts container position + NSLayoutConstraint *trailingConstraint = [NSLayoutConstraint constraintWithItem:avatarsContainer + attribute:NSLayoutAttributeTrailing + relatedBy:NSLayoutRelationEqual + toItem:avatarsContainer.superview + attribute:NSLayoutAttributeTrailing + multiplier:1.0 + constant:-RoomBubbleCellLayout.readReceiptsViewRightMargin]; + + // At the bottom, we have reactions or nothing + NSLayoutConstraint *topConstraint; + if (reactionsView) + { + topConstraint = [avatarsContainer.topAnchor constraintEqualToAnchor:reactionsView.bottomAnchor constant:RoomBubbleCellLayout.readReceiptsViewTopMargin]; + } + else + { + topConstraint = [avatarsContainer.topAnchor constraintEqualToAnchor:avatarsContainer.superview.topAnchor constant:bottomPositionY + RoomBubbleCellLayout.readReceiptsViewTopMargin]; + } + + + // Available on iOS 8 and later + [NSLayoutConstraint activateConstraints:@[widthConstraint, heightConstraint, topConstraint, trailingConstraint]]; } - - - // Available on iOS 8 and later - [NSLayoutConstraint activateConstraints:@[widthConstraint, heightConstraint, topConstraint, trailingConstraint]]; } } From 975d88ee7174cc644d59e0b5ce5cacd11d747e3a Mon Sep 17 00:00:00 2001 From: Krombel Date: Thu, 19 Dec 2019 16:48:59 +0000 Subject: [PATCH 042/282] Translated using Weblate (German) Currently translated at 98.0% (833 of 850 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/de/ --- Riot/Assets/de.lproj/Vector.strings | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Riot/Assets/de.lproj/Vector.strings b/Riot/Assets/de.lproj/Vector.strings index 3f9f78ddfe..4031f43d38 100644 --- a/Riot/Assets/de.lproj/Vector.strings +++ b/Riot/Assets/de.lproj/Vector.strings @@ -904,3 +904,5 @@ "widget_menu_revoke_permission" = "Zugriff für mich widerrufen"; "widget_menu_remove" = "Für alle entfernen"; "widget_integration_manager_disabled" = "Sie müssen den Integration Manager in den Einstellungen aktivieren"; +"settings_discovery_settings" = "ERKENNUNG"; +"settings_three_pids_management_information_part2" = "Erkennung"; From c93d7996f6f5a39b41d57f046b375db0a726a9b2 Mon Sep 17 00:00:00 2001 From: Chris Moos Date: Sun, 22 Dec 2019 14:37:39 -0700 Subject: [PATCH 043/282] Fix issue with joining public rooms with no guest access. This fixes #2888. Signed-off-by: Chris Moos --- CHANGES.rst | 6 ++++++ Riot/Model/Room/RoomPreviewData.m | 11 +++++++++-- Riot/Modules/Rooms/RoomsViewController.m | 5 ++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 3349c7ebfa..c2153c78b9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,9 @@ +Changes in 0.10.5 (TBD) +=============================================== + +Bug fix: +* Fix error when joining some public rooms. (#2888). + Changes in 0.10.4 (2019-12-11) =============================================== diff --git a/Riot/Model/Room/RoomPreviewData.m b/Riot/Model/Room/RoomPreviewData.m index a55c0eae21..b86f1b7ef3 100644 --- a/Riot/Model/Room/RoomPreviewData.m +++ b/Riot/Model/Room/RoomPreviewData.m @@ -51,12 +51,17 @@ - (instancetype)initWithPublicRoom:(MXPublicRoom*)publicRoom andSession:(MXSessi if (self) { // Report public room data - _roomName = publicRoom.name; + _roomName = publicRoom.displayname; _roomAvatarUrl = publicRoom.avatarUrl; _roomTopic = publicRoom.topic; _roomAliases = publicRoom.aliases; _numJoinedMembers = publicRoom.numJoinedMembers; + // First try to fallback to the name if displayname isn't present + if (!_roomName.length) { + _roomName = publicRoom.name; + } + if (!_roomName.length) { // Consider the room aliases to define a default room name. @@ -109,7 +114,9 @@ - (void)peekInRoom:(void (^)(BOOL succeeded))completion } failure:^(NSError *error) { MXStrongifyAndReturnIfNil(self); - self->_roomName = self->_roomId; + if(self->_roomName == nil || self->_roomName.length == 0) { + self->_roomName = self->_roomId; + } completion(NO); }]; } diff --git a/Riot/Modules/Rooms/RoomsViewController.m b/Riot/Modules/Rooms/RoomsViewController.m index e7cab93b42..09f15cc14d 100644 --- a/Riot/Modules/Rooms/RoomsViewController.m +++ b/Riot/Modules/Rooms/RoomsViewController.m @@ -267,13 +267,12 @@ - (void)openPublicRoomAtIndexPath:(NSIndexPath *)indexPath // Preview the public room if (publicRoom.worldReadable) { - RoomPreviewData *roomPreviewData = [[RoomPreviewData alloc] initWithRoomId:publicRoom.roomId andSession:recentsDataSource.publicRoomsDirectoryDataSource.mxSession]; - + RoomPreviewData *roomPreviewData = [[RoomPreviewData alloc] initWithPublicRoom:publicRoom andSession:recentsDataSource.publicRoomsDirectoryDataSource.mxSession]; + [self startActivityIndicator]; // Try to get more information about the room before opening its preview [roomPreviewData peekInRoom:^(BOOL succeeded) { - [self stopActivityIndicator]; [[AppDelegate theDelegate].masterTabBarController showRoomPreview:roomPreviewData]; From 2d731acf885497aec5d585824e1370f2f6f4b15c Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Thu, 26 Dec 2019 12:21:08 +0100 Subject: [PATCH 044/282] Update third_party_licenses by adding DGCollectionViewLeftAlignFlowLayout --- Riot/Assets/third_party_licenses.html | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Riot/Assets/third_party_licenses.html b/Riot/Assets/third_party_licenses.html index 6499f7712f..3ab25b73b1 100644 --- a/Riot/Assets/third_party_licenses.html +++ b/Riot/Assets/third_party_licenses.html @@ -1376,6 +1376,33 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +
  • + DGCollectionViewLeftAlignFlowLayout (https://github.com/Digipolitan/collection-view-left-align-flow-layout) +

    This is a simple layout that align does not try to fulfill the lines but stick elements to the left. +

    DGCollectionViewLeftAlignFlowLayout is licensed under the BSD 3-Clause license. +
    Copyright (c) 2017, Digipolitan All rights reserved. +

    Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: +

    - Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +

    - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +

    - Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. +

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +

    +
  • From 1d52a55d8a1a6889d5539df06689d663a5a89e9b Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Thu, 26 Dec 2019 23:24:09 +0100 Subject: [PATCH 045/282] Code cleaning --- Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h index 9cc1110f35..41d4c0aff7 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h @@ -65,7 +65,7 @@ typedef enum : NSUInteger `RoomInputToolbarView` instance is a view used to handle all kinds of available inputs for a room (message composer, attachments selection...). */ -@interface RoomInputToolbarView : MXKRoomInputToolbarViewWithHPGrowingText +@interface RoomInputToolbarView : MXKRoomInputToolbarViewWithHPGrowingText /** The delegate notified when inputs are ready. From 35e91da52ad2c09346119b74a29e156a86f83016 Mon Sep 17 00:00:00 2001 From: Artem Polivanchuk Date: Thu, 26 Dec 2019 09:40:42 +0000 Subject: [PATCH 046/282] Translated using Weblate (Ukrainian) Currently translated at 12.7% (108 of 850 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/uk/ --- Riot/Assets/uk.lproj/Vector.strings | 37 ++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/Riot/Assets/uk.lproj/Vector.strings b/Riot/Assets/uk.lproj/Vector.strings index 2a4ce162cb..f5f99249f1 100644 --- a/Riot/Assets/uk.lproj/Vector.strings +++ b/Riot/Assets/uk.lproj/Vector.strings @@ -52,7 +52,7 @@ "auth_repeat_password_placeholder" = "Повторіть пароль"; "auth_repeat_new_password_placeholder" = "Підтвердьте новий пароль"; "auth_home_server_placeholder" = "URL (наприклад, https://matrix.org)"; -"auth_identity_server_placeholder" = "URL (наприклад, https://matrix.org)"; +"auth_identity_server_placeholder" = "URL (наприклад, https://vector.im)"; "auth_invalid_login_param" = "Неправильне ім'я користувача або пароль"; "auth_invalid_user_name" = "Імена користувачів можуть містити лише літери, цифри, крапки, дефіси й підкреслення"; "auth_invalid_password" = "Пароль надто короткий (мінімум 6 знаків)"; @@ -81,3 +81,38 @@ "auth_recaptcha_message" = "Цей домашній сервер бажає переконатися, що ви не робот"; "auth_reset_password_message" = "Щоб відновити пароль, введіть адресу е-пошти, пов'язану з вашим обліковим записом:"; "auth_reset_password_missing_email" = "Необхідно ввести адресу е-пошти, пов'язану з вашим обліковим записом."; +// String for App Store +"store_short_description" = "Захищений, децентралізований чат/VoIP"; +"close" = "Закрити"; +// Accessibility +"accessibility_checkbox_label" = "прапорець"; +"auth_add_email_message_2" = "Вкажіть е-пошту для відновлення облікового запису, а також для можливості знаходження вас іншими користувачами."; +"auth_add_phone_message_2" = "Вкажіть номер телефону для можливості знаходження вас іншими користувачами."; +"auth_add_email_phone_message_2" = "Вкажіть е-пошту для відновлення облікового запису. Використовуйте електронну пошту чи номер телефону для можливості знаходження вас іншими користувачами."; +"auth_email_is_required" = "Ідентифікаційний сервер не налаштовано, тому ви не можете додати адресу електронної пошти, щоб мати можливість відновити пароль в майбутньому."; +"auth_phone_is_required" = "Ідентифікаційний сервер не налаштовано, тому ви не можете додати номер телефону, щоб мати можливість відновити пароль в майбутньому."; +"auth_forgot_password_error_no_configured_identity_server" = "Ідентифікаційний сервер не налаштовано: додайте його, щоб мати можливість відновити пароль в майбутньому."; +"auth_reset_password_missing_password" = "Необхідно ввести новий пароль."; +"auth_reset_password_email_validation_message" = "На адресу %@ надіслано лист. Після переходу за посиланням в листі, натисніть внизу."; +"auth_reset_password_next_step_button" = "Я підтверджую свою адресу е-пошти"; +"auth_reset_password_error_unauthorized" = "Не вдалося перевірити е-пошту: переконайтеся, що ви перейшли за посиланням у листі"; +"auth_reset_password_error_not_found" = "Схоже, ваша адреса електронної пошти не пов'язана з жодним Matrix ID на цьому домашньому сервері."; +"auth_reset_password_error_is_required" = "Ідентифікаційний сервер не налаштовано: додайте його в параметрах сервера, щоб мати можливість відновити пароль в майбутньому."; +"auth_reset_password_success_message" = "Ваш пароль було відновлено.\n\nСеанс входу завершено на всіх пристроях і припинено отримання push-сповіщень. Щоб активувати сповіщення, виконайте вхід з новим паролем на кожному пристрої."; +"auth_add_email_and_phone_warning" = "Реєстрація за допомогою електронної пошти і номера телефону водночас не підтримується, доки немає API. Лише номер телефону буде додано до облікового запису. Ви можете додати електронну пошту до свого профілю в налаштуваннях."; +"auth_accept_policies" = "Ознайомтесь та прийміть правила цього домашнього сервера:"; +"auth_softlogout_signed_out" = "Ви вийшли"; +"auth_softlogout_sign_in" = "Увійти"; +"auth_softlogout_reason" = "Адміністратор вашого домашнього сервера (%1$@) завершив сеанс вашого облікового запису %2$@ (%3$@)."; +"auth_softlogout_recover_encryption_keys" = "Увійдіть для відновлення ключів шифрування, що зберігаються тільки на цьому пристрої. Вам вони необхідні для прочитання всіх ваших захищених повідомлень на будь-якому пристрої."; +"auth_softlogout_clear_data" = "Стерти особисті дані"; +"auth_softlogout_clear_data_message_1" = "Попередження: Ваші особисті дані (включно з ключами шифрування) все ще зберігаються на цьому пристрої."; +"auth_softlogout_clear_data_message_2" = "Зітріть, якщо ви припинили користуватися цим пристроєм, або хочете увійти в інший обліковий запис."; +"auth_softlogout_clear_data_button" = "Стерти всі дані"; +"auth_softlogout_clear_data_sign_out_title" = "Ви впевнені?"; +"auth_softlogout_clear_data_sign_out_msg" = "Ви справді хочете стерти всі дані, що зберігаються на цьому пристрої? Увійдіть знову для доступу до даних і повідомлень свого облікового запису."; +"auth_softlogout_clear_data_sign_out" = "Вийти"; +"room_resource_limit_exceeded_message_contact_1" = " Будь ласка, "; +// Generic errors +"error_invite_3pid_with_no_identity_server" = "Додайте ідентифікаційний сервер в параметрах, щоб запросити е-поштою."; +"error_not_supported_on_mobile" = "Ви не можете робити це з %@ мобільного."; From e40adf233cea15ac5acafa03e2e367e38c4bf34b Mon Sep 17 00:00:00 2001 From: Artem Polivanchuk Date: Thu, 26 Dec 2019 06:52:07 +0000 Subject: [PATCH 047/282] Translated using Weblate (Ukrainian) Currently translated at 100.0% (28 of 28 strings) Translation: Riot iOS/Riot iOS (Push) Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios-push/uk/ --- Riot/Assets/uk.lproj/Localizable.strings | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Riot/Assets/uk.lproj/Localizable.strings b/Riot/Assets/uk.lproj/Localizable.strings index f3fe812653..272ade2eef 100644 --- a/Riot/Assets/uk.lproj/Localizable.strings +++ b/Riot/Assets/uk.lproj/Localizable.strings @@ -1,5 +1,5 @@ /* New message from a specific person, not referencing a room */ -"MSG_FROM_USER" = "Повідомлення від %@"; +"MSG_FROM_USER" = "%@ надсилає повідомлення"; /* New message from a specific person in a named room */ "MSG_FROM_USER_IN_ROOM" = "%@ пише в %@"; /* New message from a specific person, not referencing a room. Content included. */ @@ -11,7 +11,7 @@ /* New action message from a specific person in a named room. */ "ACTION_FROM_USER_IN_ROOM" = "%@: * %@ %@"; /* New action message from a specific person, not referencing a room. */ -"IMAGE_FROM_USER" = "%@ надсилає вам зображення %@"; +"IMAGE_FROM_USER" = "%@ надсилає зображення %@"; /* New action message from a specific person in a named room. */ "IMAGE_FROM_USER_IN_ROOM" = "%@ оприлюднює зображення %@ в %@"; /* A single unread message in a room */ @@ -50,3 +50,7 @@ "VOICE_CONF_NAMED_FROM_USER" = "Груповий виклик від %@: '%@'"; /* Incoming named video conference invite from a specific person */ "VIDEO_CONF_NAMED_FROM_USER" = "Груповий відео-виклик від %@: '%@'"; +/* Message title for a specific person in a named room */ +"MSG_FROM_USER_IN_ROOM_TITLE" = "%@ в %@"; +/* Sticker from a specific person, not referencing a room. */ +"STICKER_FROM_USER" = "%@ надсилає наліпку"; From e926ac270c78d52c66060e77787b1976e569b509 Mon Sep 17 00:00:00 2001 From: Artem Polivanchuk Date: Thu, 26 Dec 2019 09:09:01 +0000 Subject: [PATCH 048/282] Translated using Weblate (Ukrainian) Currently translated at 100.0% (5 of 5 strings) Translation: Riot iOS/Riot iOS (Dialogs) Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios-dialogs/uk/ --- Riot/Assets/uk.lproj/InfoPlist.strings | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Riot/Assets/uk.lproj/InfoPlist.strings b/Riot/Assets/uk.lproj/InfoPlist.strings index e215f29358..cf6bb7c596 100644 --- a/Riot/Assets/uk.lproj/InfoPlist.strings +++ b/Riot/Assets/uk.lproj/InfoPlist.strings @@ -2,4 +2,5 @@ "NSCameraUsageDescription" = "Камера використовується для знімків фото і відео, а також для відео-викликів."; "NSPhotoLibraryUsageDescription" = "Фотографії використовуються для надсилання фото і відео."; "NSMicrophoneUsageDescription" = "Мікрофон використовується для відео і викликів."; -"NSContactsUsageDescription" = "Щоб показати, які з ваших контактів вже використовують Riot чи Matrix, ми можемо надіслати адреси електронної пошти і номери телефонів з вашої адресної книги до вашого сервера ідентифікації Matrix. Новий Vector не зберігає і не використовує ці дані. Для докладних відомостей ознайомтеся зі сторінкою політики приватності в налаштуваннях додатку."; +"NSContactsUsageDescription" = "Щоб показати, які з ваших контактів вже використовують Matrix, Riot може надіслати адреси електронної пошти і номери телефонів з вашої адресної книги до вашого ідентифікаційного сервера Matrix. При наявності підтримки, перед надсиланням створюється хеш особистих даних. Для докладних відомостей ознайомтеся з політикою приватності свого ідентифікаційного сервера."; +"NSCalendarsUsageDescription" = "Переглядайте свої заплановані зустрічі в додатку."; From b8fbbab055835d3e8a095390eb812131b412d153 Mon Sep 17 00:00:00 2001 From: Artem Polivanchuk Date: Fri, 27 Dec 2019 06:55:50 +0000 Subject: [PATCH 049/282] Translated using Weblate (Ukrainian) Currently translated at 14.5% (123 of 850 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/uk/ --- Riot/Assets/uk.lproj/Vector.strings | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Riot/Assets/uk.lproj/Vector.strings b/Riot/Assets/uk.lproj/Vector.strings index f5f99249f1..384289cbd6 100644 --- a/Riot/Assets/uk.lproj/Vector.strings +++ b/Riot/Assets/uk.lproj/Vector.strings @@ -116,3 +116,20 @@ // Generic errors "error_invite_3pid_with_no_identity_server" = "Додайте ідентифікаційний сервер в параметрах, щоб запросити е-поштою."; "error_not_supported_on_mobile" = "Ви не можете робити це з %@ мобільного."; +// Errors +"error_user_already_logged_in" = "Схоже, ви намагаєтесь з'єднатися до іншого домашнього сервера. Хочете вийти?"; +// Chat creation +"room_creation_title" = "Новий чат"; +"room_creation_account" = "Обліковий запис"; +"room_creation_appearance" = "Зовнішній вигляд"; +"room_creation_appearance_name" = "Назва"; +"room_creation_appearance_picture" = "Зображення чату (необов'язково)"; +"room_creation_privacy" = "Приватність"; +"room_creation_private_room" = "Цей чат є приватним"; +"room_creation_public_room" = "Цей чат є публічним"; +"room_creation_make_public" = "Зробити публічним"; +"room_creation_make_public_prompt_title" = "Зробити цей чат публічним?"; +"room_creation_make_public_prompt_msg" = "Ви справді хочете зробити цей чат публічним? Будь-хто зможе долучатися й читати повідомлення."; +"room_creation_keep_private" = "Залишити приватним"; +"room_creation_make_private" = "Зробити приватним"; +"room_creation_wait_for_creation" = "Триває створення кімнати. Будь ласка, зачекайте."; From c7ac29f2a917a55ef17676138a74ee1ff5db7f1f Mon Sep 17 00:00:00 2001 From: Slavi Pantaleev Date: Thu, 2 Jan 2020 08:38:26 +0000 Subject: [PATCH 050/282] Translated using Weblate (Bulgarian) Currently translated at 100.0% (850 of 850 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/bg/ --- Riot/Assets/bg.lproj/Vector.strings | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Riot/Assets/bg.lproj/Vector.strings b/Riot/Assets/bg.lproj/Vector.strings index 9246d1c2c3..936014d808 100644 --- a/Riot/Assets/bg.lproj/Vector.strings +++ b/Riot/Assets/bg.lproj/Vector.strings @@ -923,3 +923,20 @@ "widget_menu_revoke_permission" = "Премахни достъпа за мен"; "widget_menu_remove" = "Премахни за всички"; "widget_integration_manager_disabled" = "Необходимо е да включите мениджър на интеграции от настройки"; +// Accessibility +"accessibility_checkbox_label" = "отметка"; +"settings_labs_dm_key_verification" = "Потвърждение на ключ чрез директно съобщение"; +"settings_labs_cross_signing" = "Кръстосано-подписване"; +"widget_picker_manage_integrations" = "Управление на интеграциите..."; +// Room widget permissions +"room_widget_permission_title" = "Зареждане на приспособление"; +"room_widget_permission_creator_info_title" = "Приспособлението беше добавено от:"; +"room_widget_permission_webview_information_title" = "Използването му може да сложи бисквитки и да сподели данни с %@:\n"; +"room_widget_permission_information_title" = "Използването му може да сподели данни с %@:\n"; +"room_widget_permission_display_name_permission" = "Вашето име"; +"room_widget_permission_avatar_url_permission" = "Адреса на вашата снимка"; +"room_widget_permission_user_id_permission" = "Вашето потребителското ID"; +"room_widget_permission_theme_permission" = "Вашата тема"; +"room_widget_permission_widget_id_permission" = "ID на приспособление"; +"room_widget_permission_room_id_permission" = "ID на стая"; +"service_terms_modal_policy_checkbox_accessibility_hint" = "Отметнете за да приемете %@"; From 536d4b6ff35b812cc06b2a774e7d1d194ac46cc1 Mon Sep 17 00:00:00 2001 From: Elwyn Malethan Date: Wed, 1 Jan 2020 10:54:25 +0000 Subject: [PATCH 051/282] Translated using Weblate (Welsh) Currently translated at 100.0% (850 of 850 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/cy/ --- Riot/Assets/cy.lproj/Vector.strings | 53 +++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/Riot/Assets/cy.lproj/Vector.strings b/Riot/Assets/cy.lproj/Vector.strings index 2ef812cdb7..e3fdff348a 100644 --- a/Riot/Assets/cy.lproj/Vector.strings +++ b/Riot/Assets/cy.lproj/Vector.strings @@ -92,7 +92,7 @@ "auth_reset_password_email_validation_message" = "Mae e-bost wedi'i anfon at %@. Ar ôl i chi ddilyn y ddolen sydd ynddo, cliciwch isod."; "auth_reset_password_next_step_button" = "Rwyf wedi gwirio fy nghyfeiriad e-bost"; "auth_reset_password_error_unauthorized" = "Methiant gwirio cyfeiriad e-bost: gwnewch yn siŵr eich bod wedi clicio'r ddolen yn yr e-bost"; -"auth_reset_password_error_not_found" = "Nid yw'n ymddangos bod eich cyfeiriad e-bost yn gysylltiedig ag Matrix ID ar y hafanweinydd hwn."; +"auth_reset_password_error_not_found" = "Nid yw'n ymddangos bod eich cyfeiriad e-bost yn gysylltiedig ag Dynodwr Matrix ar y hafanweinydd hwn."; "auth_reset_password_error_is_required" = "Nid oes unrhyw weinydd adnabod wedi'i osod: ychwanegwch un yn opsiynau'r gweinydd i ailosod eich cyfrinair."; "auth_reset_password_success_message" = "Mae eich cyfrinair wedi'i ailosod.\n\nRydych wedi cael eich allgofnodi o bob dyfais ac ni fyddwch yn derbyn hysbysiadau mwyach. I ail-alluogi hysbysiadau, ail-fewngofnodwch ar bob dyfais."; "auth_add_email_and_phone_warning" = "Ni chefnogir cofrestru gydag e-bost a rhif ffôn ar yr un pryd nes bod yr api yn bodoli. Dim ond y rhif ffôn fydd yn cael ei ystyried. Gallwch ychwanegu eich e-bost at eich proffil mewn gosodiadau."; @@ -126,7 +126,7 @@ "room_creation_keep_private" = "Cadw'n breifat"; "room_creation_make_private" = "Gwneud yn breifat"; "room_creation_wait_for_creation" = "Mae ystafell eisoes yn cael ei greu. Arhoswch os gwelwch yn dda."; -"room_creation_invite_another_user" = "Chwilio / gwahodd yn ôl ID defnyddiwr, Enw, new e-bost"; +"room_creation_invite_another_user" = "Chwilio / gwahodd yn ôl Dynodwr Defnyddiwr, Enw, new e-bost"; "room_creation_error_invite_user_by_email_without_identity_server" = "Nid oes unrhyw weinydd adnabod wedi'i osod felly ni allwch ychwanegu cyfranogwr gydag e-bost."; // Room recents "room_recents_directory_section" = "CYFEIRIADUR YSTAFELLOEDD"; @@ -158,7 +158,7 @@ "search_people" = "Pobl"; "search_files" = "Ffeiliau"; "search_default_placeholder" = "Chwilio"; -"search_people_placeholder" = "Chwilio yn ôl ID defnyddiwr, Enw, new e-bost"; +"search_people_placeholder" = "Chwilio yn ôl Dynodwr Defnyddiwr, Enw, new e-bost"; "search_no_result" = "Dim canlyniadau"; "search_in_progress" = "Chwilio…"; // Directory @@ -191,9 +191,9 @@ "room_participants_invite_prompt_title" = "Cadarnhad"; "room_participants_invite_prompt_msg" = "Ydych chi'n siwr eich bod eisau ychwanegu %@ i'r sgwrs?"; "room_participants_filter_room_members" = "Hidlo aelodau'r ystafell"; -"room_participants_invite_another_user" = "Chwilio / gwahodd yn ôl ID defnyddiwr, Enw, new e-bost"; +"room_participants_invite_another_user" = "Chwilio / gwahodd yn ôl Dynodwr Defnyddiwr, Enw, new e-bost"; "room_participants_invite_malformed_id_title" = "Gwall gwahoddiad"; -"room_participants_invite_malformed_id" = "ID camffurfiedig. Dylai fod yn gyfeiriad e-bost neu'n ID Matrix fel '@localpart:domain'"; +"room_participants_invite_malformed_id" = "Dynodwr camffurfiedig. Dylai fod yn gyfeiriad e-bost neu'n Ddynodwr Matrix fel '@localpart:domain'"; "room_participants_invited_section" = "GWAHODDWYD"; "room_participants_start_new_chat_error_using_user_email_without_identity_server" = "Nid oes unrhyw weinydd adnabod wedi'i osod felly ni allwch ddechrau sgwrs gyda chyswllt gan ddefnyddio e-bost."; "room_participants_online" = "Ar-lein"; @@ -417,7 +417,7 @@ "settings_add_3pid_password_message" = "I barhau, rhowch eich cyfrinair os gwelwch yn dda"; "settings_add_3pid_invalid_password_message" = "Cyfrinair annilys"; "settings_crypto_device_name" = "Enw Cyhoeddus y Ddyfais: "; -"settings_crypto_device_id" = "\nID y ddyfais: "; +"settings_crypto_device_id" = "\nDynodwr y ddyfais: "; "settings_crypto_device_key" = "\nAllwedd y ddyfais:\n"; "settings_crypto_export" = "Allfudo allweddi"; "settings_crypto_blacklist_unverified_devices" = "Amgryptio i ddyfeisiau wedi'u gwirio yn unig"; @@ -425,14 +425,14 @@ "settings_key_backup_info" = "Sicrheir negeseuon wedi'u hamgryptio gydag amgryptio o'r dechrau i'r diwedd. Dim ond chi a'r derbynnydd / derbynwyr sydd â'r allweddi i ddarllen y negeseuon hyn."; "settings_key_backup_info_checking" = "Gwirio..."; "settings_key_backup_info_none" = "Nid yw'ch allweddi yn cael eu cadw wrth gefn o'r ddyfais hon."; -"settings_key_backup_info_signout_warning" = "Cysylltwch y ddyfais hon â Allweddi Wrth Gefn cyn arwyddo allan er mwyn osgoi colli unrhyw allweddi a allai fod ar y ddyfais hon yn unig."; +"settings_key_backup_info_signout_warning" = "Cysylltwch y ddyfais hon â Allweddi Wrth Gefn cyn allgofnodi er mwyn osgoi colli unrhyw allweddi a allai fod ar y ddyfais hon yn unig."; "settings_key_backup_info_version" = "Fersiwn Allweddi Wrth Gefn: %@"; "settings_key_backup_info_algorithm" = "Algorithm: %@"; "settings_key_backup_info_valid" = "Mae'r ddyfais hon yn gwneud copi wrth gefn o'ch allweddi."; "settings_key_backup_info_not_valid" = "Nid yw'r ddyfais hon yn gwneud copi wrth gefn o'ch allweddi, ond mae gennych gopi wrth gefn y gallwch ei adfer ac ychwanegu ato wrth symud ymlaen."; "settings_key_backup_info_progress" = "Creu copi wrth gefn o allweddi %@..."; "settings_key_backup_info_progress_done" = "Pob allwedd â copi wrth gefn"; -"settings_key_backup_info_trust_signature_unknown" = "Mae gan Allweddi Wrth Gefn lofnod o'r ddyfais gydag ID: %@"; +"settings_key_backup_info_trust_signature_unknown" = "Mae gan Allweddi Wrth Gefn lofnod o'r ddyfais gyda dynodwr: %@"; "settings_key_backup_info_trust_signature_valid" = "Mae gan Allweddi Wrth Gefn lofnod dilys o'r ddyfais hon"; "settings_key_backup_info_trust_signature_valid_device_verified" = "Mae gan Allweddi Wrth Gefn lofnod dilys o %@"; "settings_key_backup_info_trust_signature_valid_device_unverified" = "Mae gan Allweddi Wrth Gefn lofnod o %@"; @@ -516,12 +516,12 @@ "room_details_addresses_disable_main_address_prompt_title" = "Rhybydd prif gyfeiriad"; "room_details_addresses_disable_main_address_prompt_msg" = "Ni fydd gennych brif gyfeiriad wedi'i nodi. Dewisir ar hap prif gyfeiriad diofyn ar gyfer yr ystafell hon"; "room_details_flair_section" = "Dangos dawn i gymunedau"; -"room_details_new_flair_placeholder" = "Ychwanegu ID cymunedol newydd (e.e. +foo%@)"; +"room_details_new_flair_placeholder" = "Ychwanegu dynodwr cymunedol newydd (e.e. +foo%@)"; "room_details_flair_invalid_id_prompt_title" = "Fformat annilys"; "room_details_flair_invalid_id_prompt_msg" = "Nid yw %@ yn ddynodwr dilys ar gyfer cymuned"; "room_details_banned_users_section" = "Defnyddwyr gwaharddedig"; "room_details_advanced_section" = "Uwch"; -"room_details_advanced_room_id" = "ID Ystafell:"; +"room_details_advanced_room_id" = "Dynodwr Ystafell:"; "room_details_advanced_enable_e2e_encryption" = "Galluogi amgryptio (rhybudd: ni ellir ei anablu eto!)"; "room_details_advanced_e2e_encryption_enabled" = "Mae amgryptio wedi'i alluogi yn yr ystafell hon"; "room_details_advanced_e2e_encryption_disabled" = "Nid yw amgryptio wedi'i alluogi yn yr ystafell hon."; @@ -542,7 +542,7 @@ "room_details_save_changes_prompt" = "Hoffech chi gadw'r newidiadau?"; "room_details_set_main_address" = "Gosod fel Prif Gyfeiriad"; "room_details_unset_main_address" = "Dad-osod fel Prif Gyfeiriad"; -"room_details_copy_room_id" = "Copio ID Ystafell"; +"room_details_copy_room_id" = "Copio Dynodwr Ystafell"; "room_details_copy_room_address" = "Copio Cyfeiriad Ystafell"; "room_details_copy_room_url" = "Copio URL Ystafell"; // Group Details @@ -565,9 +565,9 @@ "group_participants_invite_prompt_title" = "Cadarnhad"; "group_participants_invite_prompt_msg" = "Ydych chi'n siwr eich bod eisau gwahodd %@ i'r grŵp?"; "group_participants_filter_members" = "Hidlo aelodau'r cymuned"; -"group_participants_invite_another_user" = "Chwilio / gwahodd yn ôl ID Defnyddiwr neu Enw"; +"group_participants_invite_another_user" = "Chwilio / gwahodd yn ôl Dynodwr Defnyddiwr neu Enw"; "group_participants_invite_malformed_id_title" = "Gwall gwahoddiad"; -"group_participants_invite_malformed_id" = "ID camffurfiedig. Dylai fod yn ID Matrix fel '@localpart:domain'"; +"group_participants_invite_malformed_id" = "Dynodwr camffurfiedig. Dylai fod yn Ddynodwr Matrix fel '@localpart:domain'"; "group_participants_invited_section" = "GWAHODDWYD"; // Group rooms "group_rooms_filter_rooms" = "Hidlo ystafelloedd y gymuned"; @@ -693,7 +693,7 @@ "service_terms_modal_title_identity_server" = "Darganfod Cysylltiadau"; "service_terms_modal_message_identity_server" = "Derbyn telerau'r gweinydd adnabod (%@) i ddarganfod cysylltiadau."; "deactivate_account_title" = "Dad-actifadu Cyfrif"; -"deactivate_account_informations_part1" = "Bydd hyn yn golygu na ellir defnyddio'ch cyfrif yn barhaol. Ni fyddwch yn gallu mewngofnodi, ac ni fydd unrhyw un yn gallu ailgofrestru'r un ID defnyddiwr. Bydd hyn yn achosi i'ch cyfrif adael yr holl ystafelloedd y mae'n cymryd rhan ynddynt, a bydd yn tynnu manylion eich cyfrif o'ch gweinydd adnabod. "; +"deactivate_account_informations_part1" = "Bydd hyn yn golygu na ellir defnyddio'ch cyfrif yn barhaol. Ni fyddwch yn gallu mewngofnodi, ac ni fydd unrhyw un yn gallu ailgofrestru'r un dynodwr defnyddiwr. Bydd hyn yn achosi i'ch cyfrif adael yr holl ystafelloedd y mae'n cymryd rhan ynddynt, a bydd yn tynnu manylion eich cyfrif o'ch gweinydd adnabod. "; "deactivate_account_informations_part2_emphasize" = "Ni ellir gwrthdroi'r weithred hon."; "deactivate_account_informations_part3" = "\n\nYn dad-actifadu eich cyfrif "; "deactivate_account_informations_part4_emphasize" = "nid yw yn ddiofyn yn achosi inni anghofio negeseuon yr ydych wedi'u hanfon. "; @@ -883,3 +883,28 @@ // Generic errors "error_invite_3pid_with_no_identity_server" = "Ychwanegwch weinydd adnabod yn eich gosodiadau i wahodd trwy e-bost."; "error_not_supported_on_mobile" = "Ni allwch wneud hyn o %@ ffôn symudol."; +// Accessibility +"accessibility_checkbox_label" = "blwch ticio"; +"settings_integrations" = "INTEGREIDDIADAU"; +"settings_integrations_allow_button" = "Rheoli integreiddiadau"; +"settings_integrations_allow_description" = "Defnyddiwch Reolwr Integreiddio (%@) i reoli bots, pontydd, teclynnau a phecynnau sticeri.\n\nMae Rheolwyr Integreiddio yn derbyn data cyfluniad, a gallant addasu teclynnau, anfon gwahoddiadau ystafell a gosod lefelau pŵer ar eich rhan."; +"settings_labs_dm_key_verification" = "Gwirio allweddi trwy neges uniongyrchol"; +"settings_labs_cross_signing" = "Traws-arwyddo"; +"widget_menu_refresh" = "Adnewyddu"; +"widget_menu_open_outside" = "Agor mewn porwr"; +"widget_menu_revoke_permission" = "Dirymu mynediad i mi"; +"widget_menu_remove" = "Gwaredu i bawb"; +"widget_integration_manager_disabled" = "Mae angen i chi alluogi Rheolwr Integreiddio yn y gosodiadau"; +"widget_picker_manage_integrations" = "Rheoli integreiddiadau..."; +// Room widget permissions +"room_widget_permission_title" = "Llwytho Teclyn"; +"room_widget_permission_creator_info_title" = "Ychwanegwyd y teclyn hwn gan:"; +"room_widget_permission_webview_information_title" = "Gall ei ddefnyddio osod cwcis a rhannu data gyda %@:\n"; +"room_widget_permission_information_title" = "Gall ei ddefnyddio rhannu data gyda %@:\n"; +"room_widget_permission_display_name_permission" = "Eich enw arddangos"; +"room_widget_permission_avatar_url_permission" = "Eich URL dangoslun"; +"room_widget_permission_user_id_permission" = "Eich dynodwr defnyddiwr"; +"room_widget_permission_theme_permission" = "Eich thema"; +"room_widget_permission_widget_id_permission" = "Dynodwr Teclyn"; +"room_widget_permission_room_id_permission" = "Dynodwr Ystafell"; +"service_terms_modal_policy_checkbox_accessibility_hint" = "Ticiwch i dderbyn %@"; From e6c1f9a949a6c85b98da117456fabbb955e84a04 Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Thu, 9 Jan 2020 19:17:54 +0100 Subject: [PATCH 052/282] Bug Fix Key backup banner is not hidden correctly #2899 --- Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m index 8f17b3f77a..d8060b9c86 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m @@ -240,6 +240,7 @@ - (void)hideKeyBackupBanner:(KeyBackupBanner)keyBackupBanner break; } + [self updateKeyBackupBanner]; [self forceRefresh]; } From 3b93f4f46c10a8be693589be20e896507e956414 Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Thu, 9 Jan 2020 19:21:04 +0100 Subject: [PATCH 053/282] update Changes --- CHANGES.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 3349c7ebfa..73e0148611 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,12 @@ +Changes in 0.10.x (2020-xx-xx) +=============================================== + +Improvements: + * + +Bug fix: + * Key backup banner is not hidden correctly (#2899). + Changes in 0.10.4 (2019-12-11) =============================================== From c8a68fb2d8710d767b449e26a8ff2d01f603b3de Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Jan 2020 19:21:32 +0100 Subject: [PATCH 054/282] Add key verification tiles strings --- Riot/Assets/en.lproj/Vector.strings | 19 +++++++++++++ Riot/Generated/Strings.swift | 44 +++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 0117d66934..2b9f1c04d5 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -1087,3 +1087,22 @@ // Generic errors "error_invite_3pid_with_no_identity_server" = "Add an identity server in your settings to invite by email."; "error_not_supported_on_mobile" = "You can't do this from %@ mobile."; + +// MARK: - Key Verification + +// Tiles + +"key_verification_tile_request_incoming_title" = "Verification request"; +"key_verification_tile_request_outgoing_title" = "Verification sent"; + +"key_verification_tile_request_status_data_loading" = "Data loading …"; +"key_verification_tile_request_status_waiting" = "Waiting …"; +"key_verification_tile_request_status_expired" = "Expired"; +"key_verification_tile_request_status_cancelled_by_me" = "You cancel"; +"key_verification_tile_request_status_cancelled" = "%@ cancelled"; +"key_verification_tile_request_status_accepted" = "You accepted"; + +"key_verification_tile_request_incoming_approval_accept" = "Accept"; +"key_verification_tile_request_incoming_approval_decline" = "Decline"; + +"key_verification_tile_conclusion_done_title" = "Verified"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 305d37a19a..6240b5928b 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -1470,6 +1470,50 @@ internal enum VectorL10n { internal static var keyBackupSetupTitle: String { return VectorL10n.tr("Vector", "key_backup_setup_title") } + /// Verified + internal static var keyVerificationTileConclusionDoneTitle: String { + return VectorL10n.tr("Vector", "key_verification_tile_conclusion_done_title") + } + /// Accept + internal static var keyVerificationTileRequestIncomingApprovalAccept: String { + return VectorL10n.tr("Vector", "key_verification_tile_request_incoming_approval_accept") + } + /// Decline + internal static var keyVerificationTileRequestIncomingApprovalDecline: String { + return VectorL10n.tr("Vector", "key_verification_tile_request_incoming_approval_decline") + } + /// Verification request + internal static var keyVerificationTileRequestIncomingTitle: String { + return VectorL10n.tr("Vector", "key_verification_tile_request_incoming_title") + } + /// Verification sent + internal static var keyVerificationTileRequestOutgoingTitle: String { + return VectorL10n.tr("Vector", "key_verification_tile_request_outgoing_title") + } + /// You accepted + internal static var keyVerificationTileRequestStatusAccepted: String { + return VectorL10n.tr("Vector", "key_verification_tile_request_status_accepted") + } + /// %@ cancelled + internal static func keyVerificationTileRequestStatusCancelled(_ p1: String) -> String { + return VectorL10n.tr("Vector", "key_verification_tile_request_status_cancelled", p1) + } + /// You cancel + internal static var keyVerificationTileRequestStatusCancelledByMe: String { + return VectorL10n.tr("Vector", "key_verification_tile_request_status_cancelled_by_me") + } + /// Data loading … + internal static var keyVerificationTileRequestStatusDataLoading: String { + return VectorL10n.tr("Vector", "key_verification_tile_request_status_data_loading") + } + /// Expired + internal static var keyVerificationTileRequestStatusExpired: String { + return VectorL10n.tr("Vector", "key_verification_tile_request_status_expired") + } + /// Waiting … + internal static var keyVerificationTileRequestStatusWaiting: String { + return VectorL10n.tr("Vector", "key_verification_tile_request_status_waiting") + } /// %.1fK internal static func largeBadgeValueKFormat(_ p1: Float) -> String { return VectorL10n.tr("Vector", "large_badge_value_k_format", p1) From d9408dda330de74c0e133e127b9d709152bd8723 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Jan 2020 19:46:02 +0100 Subject: [PATCH 055/282] RoomBubbleCellData: Handle key verification cells. Add key verification property and add key verification cell data tags. --- .../Room/CellData/RoomBubbleCellData.h | 17 +- .../Room/CellData/RoomBubbleCellData.m | 173 +++++++++++++++--- 2 files changed, 166 insertions(+), 24 deletions(-) diff --git a/Riot/Modules/Room/CellData/RoomBubbleCellData.h b/Riot/Modules/Room/CellData/RoomBubbleCellData.h index 7f355f5b13..62d27d9989 100644 --- a/Riot/Modules/Room/CellData/RoomBubbleCellData.h +++ b/Riot/Modules/Room/CellData/RoomBubbleCellData.h @@ -22,9 +22,10 @@ typedef NS_ENUM(NSInteger, RoomBubbleCellDataTag) RoomBubbleCellDataTagMessage = 0, // Default value used for messages RoomBubbleCellDataTagMembership, RoomBubbleCellDataTagRoomCreateWithPredecessor, - RoomBubbleCellDataTagDeviceKeyVerificationRequestIncomingApproval, - RoomBubbleCellDataTagDeviceKeyVerificationRequest, - RoomBubbleCellDataTagDeviceKeyVerificationConclusion + RoomBubbleCellDataTagKeyVerificationNoDisplay, + RoomBubbleCellDataTagKeyVerificationRequestIncomingApproval, + RoomBubbleCellDataTagKeyVerificationRequest, + RoomBubbleCellDataTagKeyVerificationConclusion }; /** @@ -73,6 +74,16 @@ typedef NS_ENUM(NSInteger, RoomBubbleCellDataTag) */ @property(nonatomic, readonly) CGFloat additionalContentHeight; +/** + MXKeyVerification object associated to key verifcation event when using key verification by direct message. + */ +@property(nonatomic, strong) MXKeyVerification *keyVerification; + +/** + Indicate if there is a pending operation that updates `keyVerification` property. + */ +@property(nonatomic) BOOL isKeyVerificationOperationPending; + /** Indicate to update additional content height. */ diff --git a/Riot/Modules/Room/CellData/RoomBubbleCellData.m b/Riot/Modules/Room/CellData/RoomBubbleCellData.m index c433215b84..af30837def 100644 --- a/Riot/Modules/Room/CellData/RoomBubbleCellData.m +++ b/Riot/Modules/Room/CellData/RoomBubbleCellData.m @@ -86,23 +86,11 @@ - (instancetype)initWithEvent:(MXEvent *)event andRoomState:(MXRoomState *)roomS } } break; - case MXEventTypeKeyVerificationCancel: - case MXEventTypeKeyVerificationDone: - self.tag = RoomBubbleCellDataTagDeviceKeyVerificationConclusion; - break; - case MXEventTypeRoomMessage: - { - NSString *msgType = event.content[@"msgtype"]; - - if ([msgType isEqualToString:kMXMessageTypeKeyVerificationRequest]) - { - self.tag = RoomBubbleCellDataTagDeviceKeyVerificationRequest; - } - } - break; default: break; } + + [self keyVerificationDidUpdate]; // Increase maximum number of components self.maxComponentCount = 20; @@ -167,6 +155,16 @@ - (NSAttributedString*)attributedTextMessage return attributedTextMessage; } +- (BOOL)hasNoDisplay +{ + if (self.tag == RoomBubbleCellDataTagKeyVerificationNoDisplay) + { + return YES; + } + + return [super hasNoDisplay]; +} + #pragma mark - Bubble collapsing - (BOOL)collapseWith:(id)cellData @@ -683,21 +681,154 @@ - (BOOL)hasSameSenderAsBubbleCellData:(id)bubbleCe - (BOOL)addEvent:(MXEvent*)event andRoomState:(MXRoomState*)roomState { - if (self.tag == RoomBubbleCellDataTagMembership || event.eventType == MXEventTypeRoomMember) + BOOL shouldAddEvent = YES; + + switch (self.tag) { - // One single bubble per membership event - return NO; + case RoomBubbleCellDataTagKeyVerificationNoDisplay: + case RoomBubbleCellDataTagKeyVerificationRequest: + case RoomBubbleCellDataTagKeyVerificationRequestIncomingApproval: + case RoomBubbleCellDataTagKeyVerificationConclusion: + shouldAddEvent = NO; + break; + case RoomBubbleCellDataTagRoomCreateWithPredecessor: + // We do not want to merge room create event cells with other cell types + shouldAddEvent = NO; + break; + case RoomBubbleCellDataTagMembership: + // One single bubble per membership event + shouldAddEvent = NO; + break; + default: + break; } - if (self.tag == RoomBubbleCellDataTagRoomCreateWithPredecessor || event.eventType == MXEventTypeRoomCreate) + if (shouldAddEvent) { - // We do not want to merge room create event cells with other cell types - return NO; + switch (event.eventType) + { + case MXEventTypeRoomMessage: + { + NSString *messageType = event.content[@"msgtype"]; + + if ([messageType isEqualToString:kMXMessageTypeKeyVerificationRequest]) + { + shouldAddEvent = NO; + } + } + break; + case MXEventTypeKeyVerificationStart: + case MXEventTypeKeyVerificationAccept: + case MXEventTypeKeyVerificationKey: + case MXEventTypeKeyVerificationMac: + case MXEventTypeKeyVerificationDone: + case MXEventTypeKeyVerificationCancel: + shouldAddEvent = NO; + break; + case MXEventTypeRoomMember: + shouldAddEvent = NO; + break; + case MXEventTypeRoomCreate: + shouldAddEvent = NO; + break; + default: + break; + } + } + + if (shouldAddEvent) + { + shouldAddEvent = [super addEvent:event andRoomState:roomState]; } + + return shouldAddEvent; +} - return [super addEvent:event andRoomState:roomState]; +- (void)setKeyVerification:(MXKeyVerification *)keyVerification +{ + _keyVerification = keyVerification; + + [self keyVerificationDidUpdate]; } +- (void)keyVerificationDidUpdate +{ + MXEvent *event = self.getFirstBubbleComponentWithDisplay.event; + MXKeyVerification *keyVerification = _keyVerification; + + if (!event) + { + return; + } + + switch (event.eventType) + { + case MXEventTypeKeyVerificationCancel: + { + RoomBubbleCellDataTag cellDataTag; + + MXTransactionCancelCode *transactionCancelCode = keyVerification.transaction.reasonCancelCode; + + if (transactionCancelCode + && ([transactionCancelCode isEqual:[MXTransactionCancelCode mismatchedSas]] + || [transactionCancelCode isEqual:[MXTransactionCancelCode mismatchedKeys]] + || [transactionCancelCode isEqual:[MXTransactionCancelCode mismatchedCommitment]] + ) + ) + { + cellDataTag = RoomBubbleCellDataTagKeyVerificationConclusion; + } + else + { + cellDataTag = RoomBubbleCellDataTagKeyVerificationNoDisplay; + } + + self.tag = cellDataTag; + } + break; + case MXEventTypeKeyVerificationDone: + { + RoomBubbleCellDataTag cellDataTag; + + // Avoid to display incoming and outgoing done, only display the incoming one. + if (self.isIncoming && keyVerification && (keyVerification.state == MXKeyVerificationStateVerified)) + { + cellDataTag = RoomBubbleCellDataTagKeyVerificationConclusion; + } + else + { + cellDataTag = RoomBubbleCellDataTagKeyVerificationNoDisplay; + } + + self.tag = cellDataTag; + } + break; + case MXEventTypeRoomMessage: + { + NSString *msgType = event.content[@"msgtype"]; + + if ([msgType isEqualToString:kMXMessageTypeKeyVerificationRequest]) + { + RoomBubbleCellDataTag cellDataTag; + + if (self.isIncoming && !self.isKeyVerificationOperationPending && keyVerification && keyVerification.state == MXKeyVerificationRequestStatePending) + { + cellDataTag = RoomBubbleCellDataTagKeyVerificationRequestIncomingApproval; + } + else + { + cellDataTag = RoomBubbleCellDataTagKeyVerificationRequest; + } + + self.tag = cellDataTag; + } + } + break; + default: + break; + } + +} #pragma mark - Show all reactions From a47522b7c51b670883a625d34019019cdf81318d Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Jan 2020 19:55:49 +0100 Subject: [PATCH 056/282] MXKRoomBubbleTableViewCell: Add incoming key verification request action identifiers. --- Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h | 14 ++++++++++++++ Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m | 8 ++++++++ 2 files changed, 22 insertions(+) diff --git a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h index ca82723c61..ce1282065f 100644 --- a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h +++ b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h @@ -42,6 +42,20 @@ extern NSString *const kMXKRoomBubbleCellLongPressOnReactionView; */ extern NSString *const kMXKRoomBubbleCellEventIdKey; +/** + Action identifier used when the user pressed accept button for an incoming key verification request. + + The `userInfo` dictionary contains an `NSString` object under the `kMXKRoomBubbleCellEventIdKey` key, representing the event id associated to the key verification request. + */ +extern NSString *const kMXKRoomBubbleCellKeyVerificationIncomingRequestAcceptPressed; + +/** + Action identifier used when the user pressed decline button for an incoming key verification request. + + The `userInfo` dictionary contains an `NSString` object under the `kMXKRoomBubbleCellEventIdKey` key, representing the event id associated to the key verification request. + */ +extern NSString *const kMXKRoomBubbleCellKeyVerificationIncomingRequestDeclinePressed; + /** Define a `MXKRoomBubbleTableViewCell` category at Riot level to handle bubble customisation. */ diff --git a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m index 077bfe8b1f..6723f2dc01 100644 --- a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m +++ b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m @@ -31,6 +31,8 @@ NSString *const kMXKRoomBubbleCellTapOnReceiptsContainer = @"kMXKRoomBubbleCellTapOnReceiptsContainer"; NSString *const kMXKRoomBubbleCellLongPressOnReactionView = @"kMXKRoomBubbleCellLongPressOnReactionView"; NSString *const kMXKRoomBubbleCellEventIdKey = @"kMXKRoomBubbleCellEventIdKey"; +NSString *const kMXKRoomBubbleCellKeyVerificationIncomingRequestAcceptPressed = @"kMXKRoomBubbleCellKeyVerificationAcceptPressed"; +NSString *const kMXKRoomBubbleCellKeyVerificationIncomingRequestDeclinePressed = @"kMXKRoomBubbleCellKeyVerificationDeclinePressed"; @implementation MXKRoomBubbleTableViewCell (Riot) @@ -76,6 +78,12 @@ - (void)addTimestampLabelForComponentIndex:(NSInteger)componentIndex viewTag:(NSInteger)viewTag displayOnLeft:(BOOL)displayOnLeft { + if (!self.bubbleInfoContainer) + { + NSLog(@"[MXKRoomBubbleTableViewCell+Riot] bubbleInfoContainer property is missing for cell class: %@", NSStringFromClass(self.class)); + return; + } + NSArray *bubbleComponents = bubbleData.bubbleComponents; MXKRoomBubbleComponent *component = bubbleComponents[componentIndex]; From 194e314afe721d748735114c68b9a723a8f74b05 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Jan 2020 19:56:56 +0100 Subject: [PATCH 057/282] Add RoomBubbleCellData.h and MXKRoomBubbleTableViewCell+Riot.h to Objective-C bridging header. --- Riot/SupportingFiles/Riot-Bridging-Header.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Riot/SupportingFiles/Riot-Bridging-Header.h b/Riot/SupportingFiles/Riot-Bridging-Header.h index be5547efb2..c39876075e 100644 --- a/Riot/SupportingFiles/Riot-Bridging-Header.h +++ b/Riot/SupportingFiles/Riot-Bridging-Header.h @@ -15,3 +15,5 @@ #import "EventFormatter.h" #import "MediaPickerViewController.h" #import "AppDelegate.h" +#import "RoomBubbleCellData.h" +#import "MXKRoomBubbleTableViewCell+Riot.h" From 05d0ab7ff4077780e8f8098ec41537f02e634e56 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Jan 2020 19:58:16 +0100 Subject: [PATCH 058/282] EventFormatter: Make key verification cancel and done event types visible in timeline. --- Riot/Utils/EventFormatter.m | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Riot/Utils/EventFormatter.m b/Riot/Utils/EventFormatter.m index 3ba09e3183..f9227d3916 100644 --- a/Riot/Utils/EventFormatter.m +++ b/Riot/Utils/EventFormatter.m @@ -143,6 +143,13 @@ - (NSAttributedString *)attributedStringFromEvent:(MXEvent *)event withRoomState } } + // Make event types MXEventTypeKeyVerificationCancel and MXEventTypeKeyVerificationDone visible in timeline. + // TODO: Find another way to keep them visible and avoid instantiate empty NSMutableAttributedString. + if (event.eventType == MXEventTypeKeyVerificationCancel || event.eventType == MXEventTypeKeyVerificationDone) + { + return [NSMutableAttributedString new]; + } + NSAttributedString *attributedString = [super attributedStringFromEvent:event withRoomState:roomState error:error]; if (event.sentState == MXEventSentStateSent From 6fe6067529c104d6bf7eea780145dbb5af08db4c Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Jan 2020 20:02:34 +0100 Subject: [PATCH 059/282] Refactor BubbleCellWithoutSenderInfoContentView to BubbleCellContentView and handle pagination title. --- ...View.swift => BubbleCellContentView.swift} | 27 +++- .../BubbleCellContentView.xib | 148 ++++++++++++++++++ ...BubbleCellWithoutSenderInfoContentView.xib | 93 ----------- 3 files changed, 170 insertions(+), 98 deletions(-) rename Riot/Modules/Room/Views/BubbleCells/BaseContentViews/{BubbleCellWithoutSenderInfoContentView.swift => BubbleCellContentView.swift} (68%) create mode 100644 Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellContentView.xib delete mode 100644 Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellWithoutSenderInfoContentView.xib diff --git a/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellWithoutSenderInfoContentView.swift b/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellContentView.swift similarity index 68% rename from Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellWithoutSenderInfoContentView.swift rename to Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellContentView.swift index e4c7a9bd87..158df123c5 100644 --- a/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellWithoutSenderInfoContentView.swift +++ b/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellContentView.swift @@ -18,7 +18,7 @@ import UIKit import Reusable @objcMembers -final class BubbleCellWithoutSenderInfoContentView: UIView, NibLoadable { +final class BubbleCellContentView: UIView, NibLoadable { // MARK: - Properties @@ -34,32 +34,49 @@ final class BubbleCellWithoutSenderInfoContentView: UIView, NibLoadable { @IBOutlet weak var bubbleOverlayContainer: UIView! + @IBOutlet weak var paginationTitleContainerView: UIView! + @IBOutlet weak var paginationLabel: UILabel! + @IBOutlet weak var paginationSeparatorView: UIView! + // MARK: Private private var showReadReceipts: Bool { get { - return self.readReceiptsContainerView.isHidden + return !self.readReceiptsContainerView.isHidden } set { self.readReceiptsContainerView.isHidden = !newValue } } + // MARK: Public + + var showPaginationTitle: Bool { + get { + return !self.paginationTitleContainerView.isHidden + } + set { + self.paginationTitleContainerView.isHidden = !newValue + } + } + // MARK: - Setup - class func instantiate() -> BubbleCellWithoutSenderInfoContentView { - return BubbleCellWithoutSenderInfoContentView.loadFromNib() + class func instantiate() -> BubbleCellContentView { + return BubbleCellContentView.loadFromNib() } // MARK: - Public func update(theme: Theme) { self.backgroundColor = theme.backgroundColor + self.paginationLabel.textColor = theme.tintColor + self.paginationSeparatorView.backgroundColor = theme.tintColor } } // MARK: - BubbleCellReadReceiptsDisplayable -extension BubbleCellWithoutSenderInfoContentView: BubbleCellReadReceiptsDisplayable { +extension BubbleCellContentView: BubbleCellReadReceiptsDisplayable { func addReadReceiptsView(_ readReceiptsView: UIView) { self.readReceiptsContentView.vc_removeAllSubviews() diff --git a/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellContentView.xib b/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellContentView.xib new file mode 100644 index 0000000000..774a36c25a --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellContentView.xib @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellWithoutSenderInfoContentView.xib b/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellWithoutSenderInfoContentView.xib deleted file mode 100644 index ac074f0caf..0000000000 --- a/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellWithoutSenderInfoContentView.xib +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 28bd41f19711aac386cfa1e30dd619cc9c871972 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Jan 2020 20:10:13 +0100 Subject: [PATCH 060/282] KeyVerificationBaseBubbleCell: Use BubbleCellContentView. Improve cell height caching. --- .../KeyVerificationBaseBubbleCell.swift | 74 ++++++++++++++----- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationBaseBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationBaseBubbleCell.swift index a280a979e2..0344b20b9d 100644 --- a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationBaseBubbleCell.swift +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationBaseBubbleCell.swift @@ -30,11 +30,12 @@ class KeyVerificationBaseBubbleCell: MXKRoomBubbleTableViewCell { // MARK: Public weak var keyVerificationCellInnerContentView: KeyVerificationCellInnerContentView? - weak var bubbleCellWithoutSenderInfoContentView: BubbleCellWithoutSenderInfoContentView? + + weak var bubbleCellContentView: BubbleCellContentView? override var bubbleInfoContainer: UIView! { get { - guard let infoContainer = self.bubbleCellWithoutSenderInfoContentView?.bubbleInfoContainer else { + guard let infoContainer = self.bubbleCellContentView?.bubbleInfoContainer else { fatalError("[KeyVerificationBaseBubbleCell] bubbleInfoContainer should not be used before set") } return infoContainer @@ -46,7 +47,7 @@ class KeyVerificationBaseBubbleCell: MXKRoomBubbleTableViewCell { override var bubbleOverlayContainer: UIView! { get { - guard let overlayContainer = self.bubbleCellWithoutSenderInfoContentView?.bubbleOverlayContainer else { + guard let overlayContainer = self.bubbleCellContentView?.bubbleOverlayContainer else { fatalError("[KeyVerificationBaseBubbleCell] bubbleOverlayContainer should not be used before set") } return overlayContainer @@ -58,7 +59,7 @@ class KeyVerificationBaseBubbleCell: MXKRoomBubbleTableViewCell { override var bubbleInfoContainerTopConstraint: NSLayoutConstraint! { get { - guard let infoContainerTopConstraint = self.bubbleCellWithoutSenderInfoContentView?.bubbleInfoContainerTopConstraint else { + guard let infoContainerTopConstraint = self.bubbleCellContentView?.bubbleInfoContainerTopConstraint else { fatalError("[KeyVerificationBaseBubbleCell] bubbleInfoContainerTopConstraint should not be used before set") } return infoContainerTopConstraint @@ -91,7 +92,7 @@ class KeyVerificationBaseBubbleCell: MXKRoomBubbleTableViewCell { // MARK: - Public func update(theme: Theme) { - self.bubbleCellWithoutSenderInfoContentView?.update(theme: theme) + self.bubbleCellContentView?.update(theme: theme) self.keyVerificationCellInnerContentView?.update(theme: theme) } @@ -120,13 +121,35 @@ class KeyVerificationBaseBubbleCell: MXKRoomBubbleTableViewCell { return senderDisplayName } - class func sizingView() -> MXKRoomBubbleTableViewCell { - fatalError("[KeyVerificationBaseBubbleCell] Subclass should implement this method") + class func sizingView() -> KeyVerificationBaseBubbleCell { + fatalError("[KeyVerificationBaseBubbleCell] Subclass should implement this method") } - // TODO: Implement thiscmethod in subclasses - class func sizingHeightHashValue(from bubbleData: MXKRoomBubbleCellData) -> Int { - return bubbleData.hashValue + class func sizingViewHeightHashValue(from bubbleCellData: MXKRoomBubbleCellData) -> Int { + + var hasher = Hasher() + + let sizingView = self.sizingView() + sizingView.render(bubbleCellData) + + // Add cell class name + hasher.combine(self.defaultReuseIdentifier()) + + if let keyVerificationCellInnerContentView = sizingView.keyVerificationCellInnerContentView { + + // Add other user info + if let otherUserInfo = keyVerificationCellInnerContentView.otherUserInfo { + hasher.combine(otherUserInfo) + } + + // Add request status text + if keyVerificationCellInnerContentView.isRequestStatusHidden == false, + let requestStatusText = sizingView.keyVerificationCellInnerContentView?.requestStatusText { + hasher.combine(requestStatusText) + } + } + + return hasher.finalize() } // MARK: - Overrides @@ -163,20 +186,31 @@ class KeyVerificationBaseBubbleCell: MXKRoomBubbleTableViewCell { return height } + override func render(_ cellData: MXKCellData!) { + super.render(cellData) + + if let bubbleData = self.bubbleData, + let bubbleCellContentView = self.bubbleCellContentView, + let paginationDate = bubbleData.date, + bubbleCellContentView.showPaginationTitle { + bubbleCellContentView.paginationLabel.text = bubbleData.eventFormatter.dateString(from: paginationDate, withTime: false)?.uppercased() + } + } + // MARK: - Private private func setupContentView() { - if self.bubbleCellWithoutSenderInfoContentView == nil { + if self.bubbleCellContentView == nil { - let bubbleCellWithoutSenderInfoContentView = BubbleCellWithoutSenderInfoContentView.instantiate() + let bubbleCellContentView = BubbleCellContentView.instantiate() let innerContentView = KeyVerificationCellInnerContentView.instantiate() - bubbleCellWithoutSenderInfoContentView.innerContentView.vc_addSubViewMatchingParent(innerContentView) + bubbleCellContentView.innerContentView.vc_addSubViewMatchingParent(innerContentView) - self.contentView.vc_addSubViewMatchingParent(bubbleCellWithoutSenderInfoContentView) + self.contentView.vc_addSubViewMatchingParent(bubbleCellContentView) - self.bubbleCellWithoutSenderInfoContentView = bubbleCellWithoutSenderInfoContentView + self.bubbleCellContentView = bubbleCellContentView self.keyVerificationCellInnerContentView = innerContentView } } @@ -204,7 +238,11 @@ class KeyVerificationBaseBubbleCell: MXKRoomBubbleTableViewCell { sizingView.layoutIfNeeded() let fittingSize = CGSize(width: width, height: UIView.layoutFittingCompressedSize.height) - let height = sizingView.systemLayoutSizeFitting(fittingSize).height + var height = sizingView.systemLayoutSizeFitting(fittingSize).height + + if let roomBubbleCellData = cellData as? RoomBubbleCellData, let readReceipts = roomBubbleCellData.readReceipts, readReceipts.count > 0 { + height+=RoomBubbleCellLayout.readReceiptsViewHeight + } return height } @@ -214,10 +252,10 @@ class KeyVerificationBaseBubbleCell: MXKRoomBubbleTableViewCell { extension KeyVerificationBaseBubbleCell: BubbleCellReadReceiptsDisplayable { func addReadReceiptsView(_ readReceiptsView: UIView) { - self.bubbleCellWithoutSenderInfoContentView?.addReadReceiptsView(readReceiptsView) + self.bubbleCellContentView?.addReadReceiptsView(readReceiptsView) } func removeReadReceiptsView() { - self.bubbleCellWithoutSenderInfoContentView?.removeReadReceiptsView() + self.bubbleCellContentView?.removeReadReceiptsView() } } From 148c0686fa7aa7872f435f1a3f43dc6c458e8894 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Jan 2020 20:14:17 +0100 Subject: [PATCH 061/282] Handle key verification cells data filling. --- .../KeyVerificationCellInnerContentView.swift | 33 ++++-- .../KeyVerificationCellInnerContentView.xib | 100 +++++++++++------- .../KeyVerificationConclusionBubbleCell.swift | 45 +++++--- ...ionIncomingRequestApprovalBubbleCell.swift | 25 +++-- ...yVerificationRequestStatusBubbleCell.swift | 66 +++++++++--- 5 files changed, 181 insertions(+), 88 deletions(-) diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationCellInnerContentView.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationCellInnerContentView.swift index 4b859339f1..1402be19fd 100644 --- a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationCellInnerContentView.swift +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationCellInnerContentView.swift @@ -23,7 +23,7 @@ final class KeyVerificationCellInnerContentView: UIView, NibLoadable { private enum Constants { static let cornerRadius: CGFloat = 8.0 - static let buttonBackgroundColorAlpha: CGFloat = 0.8 + static let buttonBackgroundColorAlpha: CGFloat = 0.2 static let buttonCornerRadius: CGFloat = 6.0 } @@ -34,10 +34,11 @@ final class KeyVerificationCellInnerContentView: UIView, NibLoadable { @IBOutlet private weak var badgeImageView: UIImageView! @IBOutlet private weak var titleLabel: UILabel! - @IBOutlet private weak var userInformationsLabel: UILabel! + @IBOutlet private weak var otherUserInformationLabel: UILabel! @IBOutlet private weak var requestStatusLabel: UILabel! + @IBOutlet private weak var buttonsContainerView: UIView! @IBOutlet private weak var acceptButton: UIButton! @IBOutlet private weak var declineButton: UIButton! @@ -48,8 +49,7 @@ final class KeyVerificationCellInnerContentView: UIView, NibLoadable { return self.acceptButton.isHidden && self.declineButton.isHidden } set { - self.acceptButton.isHidden = newValue - self.declineButton.isHidden = newValue + self.buttonsContainerView.isHidden = newValue } } @@ -80,6 +80,10 @@ final class KeyVerificationCellInnerContentView: UIView, NibLoadable { } } + var otherUserInfo: String? { + return self.otherUserInformationLabel.text + } + var requestStatusText: String? { get { return self.requestStatusLabel.text @@ -106,6 +110,21 @@ final class KeyVerificationCellInnerContentView: UIView, NibLoadable { super.awakeFromNib() self.layer.masksToBounds = true + self.acceptButton.layer.masksToBounds = true + + self.acceptButton.titleLabel?.adjustsFontSizeToFitWidth = true + self.acceptButton.titleLabel?.minimumScaleFactor = 0.5 + self.acceptButton.titleLabel?.baselineAdjustment = .alignCenters + + self.acceptButton.setTitle(VectorL10n.keyVerificationTileRequestIncomingApprovalAccept, for: .normal) + + self.declineButton.layer.masksToBounds = true + + self.declineButton.titleLabel?.adjustsFontSizeToFitWidth = true + self.declineButton.titleLabel?.minimumScaleFactor = 0.5 + self.declineButton.titleLabel?.baselineAdjustment = .alignCenters + + self.declineButton.setTitle(VectorL10n.keyVerificationTileRequestIncomingApprovalDecline, for: .normal) } override func layoutSubviews() { @@ -124,14 +143,14 @@ final class KeyVerificationCellInnerContentView: UIView, NibLoadable { func update(theme: Theme) { self.backgroundColor = theme.headerBackgroundColor self.titleLabel.textColor = theme.textPrimaryColor - self.userInformationsLabel.textColor = theme.textSecondaryColor + self.otherUserInformationLabel.textColor = theme.textSecondaryColor self.acceptButton.vc_setBackgroundColor(theme.tintColor.withAlphaComponent(Constants.buttonBackgroundColorAlpha), for: .normal) self.declineButton.vc_setBackgroundColor(theme.noticeColor.withAlphaComponent(Constants.buttonBackgroundColorAlpha), for: .normal) } func updateSenderInfo(with userId: String, userDisplayName: String?) { - self.userInformationsLabel.text = self.buildUserInfoText(with: userId, userDisplayName: userDisplayName) + self.otherUserInformationLabel.text = self.buildUserInfoText(with: userId, userDisplayName: userDisplayName) } // MARK: - Private @@ -149,6 +168,8 @@ final class KeyVerificationCellInnerContentView: UIView, NibLoadable { return userInfoText } + // MARK: - Action + @IBAction private func declineButtonAction(_ sender: Any) { self.declineActionHandler?() } diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationCellInnerContentView.xib b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationCellInnerContentView.xib index ca7a98a8c1..404d95945b 100644 --- a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationCellInnerContentView.xib +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationCellInnerContentView.xib @@ -1,10 +1,6 @@ - - - - @@ -12,64 +8,85 @@ - + - - + + - + - - + + - - + + + @@ -85,12 +102,13 @@ + + - - + diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionBubbleCell.swift index 6bf6b0a5d4..41761b3808 100644 --- a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionBubbleCell.swift +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionBubbleCell.swift @@ -17,8 +17,8 @@ import UIKit @objcMembers -final class KeyVerificationConclusionBubbleCell: KeyVerificationBaseBubbleCell { - +class KeyVerificationConclusionBubbleCell: KeyVerificationBaseBubbleCell { + // MARK: - Constants private enum Sizing { @@ -47,7 +47,7 @@ final class KeyVerificationConclusionBubbleCell: KeyVerificationBaseBubbleCell { super.render(cellData) guard let keyVerificationCellInnerContentView = self.keyVerificationCellInnerContentView, - let bubbleData = self.bubbleData, + let bubbleData = self.bubbleData as? RoomBubbleCellData, let viewData = self.viewData(from: bubbleData) else { NSLog("[KeyVerificationConclusionBubbleCell] Fail to render \(String(describing: cellData))") return @@ -58,37 +58,52 @@ final class KeyVerificationConclusionBubbleCell: KeyVerificationBaseBubbleCell { keyVerificationCellInnerContentView.updateSenderInfo(with: viewData.senderId, userDisplayName: viewData.senderDisplayName) } - override class func sizingView() -> MXKRoomBubbleTableViewCell { + override class func sizingView() -> KeyVerificationBaseBubbleCell { return self.Sizing.view } // MARK: - Private - // TODO: Handle view data filling - private func viewData(from bubbleData: MXKRoomBubbleCellData) -> KeyVerificationConclusionViewData? { - guard let event = bubbleData.bubbleComponents.first?.event else { + private func viewData(from roomBubbleData: RoomBubbleCellData) -> KeyVerificationConclusionViewData? { + guard let event = roomBubbleData.bubbleComponents.first?.event else { return nil } - + let viewData: KeyVerificationConclusionViewData? - + let senderId = self.senderId(from: bubbleData) let senderDisplayName = self.senderDisplayName(from: bubbleData) let title: String? - let badgeImage: UIImage? - + let badgeImage: UIImage? + switch event.eventType { case .keyVerificationDone: - title = "Verified" badgeImage = Asset.Images.encryptionTrusted.image + title = VectorL10n.keyVerificationTileConclusionDoneTitle case .keyVerificationCancel: - title = "Cancelled" badgeImage = Asset.Images.encryptionNormal.image + + // TODO: Use right titles here + if let keyVerification = roomBubbleData.keyVerification, let cancelCodeValue = keyVerification.transaction?.reasonCancelCode?.value { + switch cancelCodeValue { + case MXTransactionCancelCode.mismatchedSas().value: + title = "TODO" + case MXTransactionCancelCode.unexpectedMessage().value: + title = "TODO" + case MXTransactionCancelCode.mismatchedCommitment().value: + title = "TODO" + default: + title = nil + } + } else { + title = nil + } + default: badgeImage = nil title = nil } - + if let title = title, let badgeImage = badgeImage { viewData = KeyVerificationConclusionViewData(badgeImage: badgeImage, title: title, @@ -97,7 +112,7 @@ final class KeyVerificationConclusionBubbleCell: KeyVerificationBaseBubbleCell { } else { viewData = nil } - + return viewData } } diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationIncomingRequestApprovalBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationIncomingRequestApprovalBubbleCell.swift index 9cc1c01363..3093b5206d 100644 --- a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationIncomingRequestApprovalBubbleCell.swift +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationIncomingRequestApprovalBubbleCell.swift @@ -17,12 +17,12 @@ import UIKit @objcMembers -final class KeyVerificationIncomingRequestApprovalBubbleCell: KeyVerificationBaseBubbleCell { - +class KeyVerificationIncomingRequestApprovalBubbleCell: KeyVerificationBaseBubbleCell { + // MARK: - Constants private enum Sizing { - static let view = KeyVerificationConclusionBubbleCell(style: .default, reuseIdentifier: nil) + static let view = KeyVerificationIncomingRequestApprovalBubbleCell(style: .default, reuseIdentifier: nil) } // MARK: - Setup @@ -68,29 +68,34 @@ final class KeyVerificationIncomingRequestApprovalBubbleCell: KeyVerificationBas keyVerificationCellInnerContentView.title = viewData.title keyVerificationCellInnerContentView.updateSenderInfo(with: viewData.senderId, userDisplayName: viewData.senderDisplayName) + let actionUserInfo: [AnyHashable: Any]? + + if let eventId = bubbleData.getFirstBubbleComponentWithDisplay()?.event.eventId { + actionUserInfo = [kMXKRoomBubbleCellEventIdKey: eventId] + } else { + actionUserInfo = nil + } + keyVerificationCellInnerContentView.acceptActionHandler = { [weak self] in - // TODO: Use correct action identifier - self?.delegate?.cell(self, didRecognizeAction: kMXKRoomBubbleCellTapOnContentView, userInfo: nil) + self?.delegate?.cell(self, didRecognizeAction: kMXKRoomBubbleCellKeyVerificationIncomingRequestAcceptPressed, userInfo: actionUserInfo) } keyVerificationCellInnerContentView.declineActionHandler = { [weak self] in - // TODO: Use correct action identifier - self?.delegate?.cell(self, didRecognizeAction: kMXKRoomBubbleCellTapOnContentView, userInfo: nil) + self?.delegate?.cell(self, didRecognizeAction: kMXKRoomBubbleCellKeyVerificationIncomingRequestDeclinePressed, userInfo: actionUserInfo) } } - override class func sizingView() -> MXKRoomBubbleTableViewCell { + override class func sizingView() -> KeyVerificationBaseBubbleCell { return self.Sizing.view } // MARK: - Private - // TODO: Handle view data filling private func viewData(from bubbleData: MXKRoomBubbleCellData) -> KeyVerificationIncomingRequestApprovalViewData? { let senderId = self.senderId(from: bubbleData) let senderDisplayName = self.senderDisplayName(from: bubbleData) - let title = "Verification request" + let title = VectorL10n.keyVerificationTileRequestIncomingTitle return KeyVerificationIncomingRequestApprovalViewData(title: title, senderId: senderId, diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationRequestStatusBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationRequestStatusBubbleCell.swift index 453407ced1..b5af25a5ac 100644 --- a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationRequestStatusBubbleCell.swift +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationRequestStatusBubbleCell.swift @@ -17,8 +17,8 @@ import UIKit @objcMembers -final class KeyVerificationRequestStatusBubbleCell: KeyVerificationBaseBubbleCell { - +class KeyVerificationRequestStatusBubbleCell: KeyVerificationBaseBubbleCell { + // MARK: - Constants private enum Sizing { @@ -52,8 +52,8 @@ final class KeyVerificationRequestStatusBubbleCell: KeyVerificationBaseBubbleCel super.render(cellData) guard let keyVerificationCellInnerContentView = self.keyVerificationCellInnerContentView, - let bubbleData = self.bubbleData, - let viewData = self.viewData(from: bubbleData) else { + let roomBubbleCellData = self.bubbleData as? RoomBubbleCellData, + let viewData = self.viewData(from: roomBubbleCellData) else { NSLog("[KeyVerificationRequestStatusBubbleCell] Fail to render \(String(describing: cellData))") return } @@ -63,29 +63,63 @@ final class KeyVerificationRequestStatusBubbleCell: KeyVerificationBaseBubbleCel keyVerificationCellInnerContentView.requestStatusText = viewData.statusText } - override class func sizingView() -> MXKRoomBubbleTableViewCell { + override class func sizingView() -> KeyVerificationBaseBubbleCell { return self.Sizing.view } // MARK: - Private - // TODO: Handle view data filling - private func viewData(from bubbleData: MXKRoomBubbleCellData) -> KeyVerificationRequestStatusViewData? { + private func viewData(from roomBubbleCellData: RoomBubbleCellData) -> KeyVerificationRequestStatusViewData? { let senderId = self.senderId(from: bubbleData) - let senderDisplayName = self.senderDisplayName(from: bubbleData) + let senderDisplayName = self.senderDisplayName(from: bubbleData) let title: String - let statusText: String = "You accepted" + let statusText: String? + + if roomBubbleCellData.isIncoming { + title = VectorL10n.keyVerificationTileRequestIncomingTitle + } else { + title = VectorL10n.keyVerificationTileRequestOutgoingTitle + } + + if let keyVerification = roomBubbleCellData.keyVerification { + switch keyVerification.state { + case .requestPending: + if !roomBubbleCellData.isIncoming { + statusText = VectorL10n.keyVerificationTileRequestStatusWaiting + } else { + if roomBubbleCellData.isKeyVerificationOperationPending { + statusText = VectorL10n.keyVerificationTileRequestStatusDataLoading + } else { + // Should not happen, KeyVerificationIncomingRequestApprovalBubbleCell should be displayed in this case. + statusText = nil + } + } + case .requestExpired: + statusText = VectorL10n.keyVerificationTileRequestStatusExpired + case .requestCancelled, .transactionCancelled: + let userName = senderDisplayName ?? senderId + statusText = VectorL10n.keyVerificationTileRequestStatusCancelled(userName) + case .requestCancelledByMe, .transactionCancelledByMe: + statusText = VectorL10n.keyVerificationTileRequestStatusCancelledByMe + default: + statusText = VectorL10n.keyVerificationTileRequestStatusAccepted + } + } else { + statusText = VectorL10n.keyVerificationTileRequestStatusDataLoading + } + + let viewData: KeyVerificationRequestStatusViewData? - if senderId.isEmpty == false { - title = "Verification request" + if let statusText = statusText { + viewData = KeyVerificationRequestStatusViewData(title: title, + senderId: senderId, + senderDisplayName: senderDisplayName, + statusText: statusText) } else { - title = "Verification sent" + viewData = nil } - return KeyVerificationRequestStatusViewData(title: title, - senderId: senderId, - senderDisplayName: senderDisplayName, - statusText: statusText) + return viewData } } From d290938fb10d9d9b6b5ee1bfc0938d75db020220 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Jan 2020 20:15:28 +0100 Subject: [PATCH 062/282] Add key verification cells with pagination title. --- ...clusionWithPaginationTitleBubbleCell.swift | 52 +++++++++++++++++++ ...pprovalWithPaginationTitleBubbleCell.swift | 52 +++++++++++++++++++ ...tStatusWithPaginationTitleBubbleCell.swift | 52 +++++++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionWithPaginationTitleBubbleCell.swift create mode 100644 Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.swift create mode 100644 Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationRequestStatusWithPaginationTitleBubbleCell.swift diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionWithPaginationTitleBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionWithPaginationTitleBubbleCell.swift new file mode 100644 index 0000000000..7cc1b8277f --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionWithPaginationTitleBubbleCell.swift @@ -0,0 +1,52 @@ +/* + Copyright 2019 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 + +@objcMembers +final class KeyVerificationConclusionWithPaginationTitleBubbleCell: KeyVerificationConclusionBubbleCell { + + // MARK: - Constants + + private enum Sizing { + static let view = KeyVerificationConclusionWithPaginationTitleBubbleCell(style: .default, reuseIdentifier: nil) + } + + // MARK: - Setup + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + self.commonInit() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func commonInit() { + guard let bubbleCellContentView = self.bubbleCellContentView else { + fatalError("[KeyVerificationConclusionWithPaginationTitleBubbleCell] bubbleCellContentView should not be nil") + } + + bubbleCellContentView.showPaginationTitle = true + } + + // MARK: - Overrides + + override class func sizingView() -> KeyVerificationBaseBubbleCell { + return self.Sizing.view + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.swift new file mode 100644 index 0000000000..6b4b529f1f --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.swift @@ -0,0 +1,52 @@ +/* + Copyright 2019 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 + +@objcMembers +final class KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell: KeyVerificationIncomingRequestApprovalBubbleCell { + + // MARK: - Constants + + private enum Sizing { + static let view = KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell(style: .default, reuseIdentifier: nil) + } + + // MARK: - Setup + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + self.commonInit() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func commonInit() { + guard let bubbleCellContentView = self.bubbleCellContentView else { + fatalError("[KeyVerificationRequestStatusWithPaginationTitleBubbleCell] bubbleCellContentView should not be nil") + } + + bubbleCellContentView.showPaginationTitle = true + } + + // MARK: - Overrides + + override class func sizingView() -> KeyVerificationBaseBubbleCell { + return self.Sizing.view + } +} diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationRequestStatusWithPaginationTitleBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationRequestStatusWithPaginationTitleBubbleCell.swift new file mode 100644 index 0000000000..f9c0e6465c --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationRequestStatusWithPaginationTitleBubbleCell.swift @@ -0,0 +1,52 @@ +/* + Copyright 2019 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 + +@objcMembers +final class KeyVerificationRequestStatusWithPaginationTitleBubbleCell: KeyVerificationRequestStatusBubbleCell { + + // MARK: - Constants + + private enum Sizing { + static let view = KeyVerificationRequestStatusWithPaginationTitleBubbleCell(style: .default, reuseIdentifier: nil) + } + + // MARK: - Setup + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + self.commonInit() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func commonInit() { + guard let bubbleCellContentView = self.bubbleCellContentView else { + fatalError("[KeyVerificationRequestStatusWithPaginationTitleBubbleCell] bubbleCellContentView should not be nil") + } + + bubbleCellContentView.showPaginationTitle = true + } + + // MARK: - Overrides + + override class func sizingView() -> KeyVerificationBaseBubbleCell { + return self.Sizing.view + } +} From 3ea3f1f84728dd4b8ab9f4e77a446580dcc4e4fe Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Jan 2020 20:23:36 +0100 Subject: [PATCH 063/282] RoomViewController: Handle key verification cells with pagination title. Handle key verification incoming request approval actions. Remove copy action in context menu for key verification cells. --- Riot/Modules/Room/RoomViewController.m | 70 +++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 7 deletions(-) diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 03ccc1656c..4053321bef 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -354,8 +354,12 @@ - (void)viewDidLoad [self.bubblesTableView registerClass:RoomPredecessorBubbleCell.class forCellReuseIdentifier:RoomPredecessorBubbleCell.defaultReuseIdentifier]; [self.bubblesTableView registerClass:KeyVerificationIncomingRequestApprovalBubbleCell.class forCellReuseIdentifier:KeyVerificationIncomingRequestApprovalBubbleCell.defaultReuseIdentifier]; + [self.bubblesTableView registerClass:KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.class forCellReuseIdentifier:KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.defaultReuseIdentifier]; [self.bubblesTableView registerClass:KeyVerificationRequestStatusBubbleCell.class forCellReuseIdentifier:KeyVerificationRequestStatusBubbleCell.defaultReuseIdentifier]; + [self.bubblesTableView registerClass:KeyVerificationRequestStatusWithPaginationTitleBubbleCell.class forCellReuseIdentifier:KeyVerificationRequestStatusWithPaginationTitleBubbleCell.defaultReuseIdentifier]; [self.bubblesTableView registerClass:KeyVerificationConclusionBubbleCell.class forCellReuseIdentifier:KeyVerificationConclusionBubbleCell.defaultReuseIdentifier]; + [self.bubblesTableView registerClass:KeyVerificationConclusionWithPaginationTitleBubbleCell.class forCellReuseIdentifier:KeyVerificationConclusionWithPaginationTitleBubbleCell.defaultReuseIdentifier]; + // Prepare expanded header expandedHeader = [ExpandedRoomTitleView roomTitleView]; @@ -2043,17 +2047,17 @@ - (void)displayRoomPreview:(RoomPreviewData *)previewData { cellViewClass = RoomPredecessorBubbleCell.class; } - else if (bubbleData.tag == RoomBubbleCellDataTagDeviceKeyVerificationRequestIncomingApproval) + else if (bubbleData.tag == RoomBubbleCellDataTagKeyVerificationRequestIncomingApproval) { - cellViewClass = KeyVerificationIncomingRequestApprovalBubbleCell.class; + cellViewClass = bubbleData.isPaginationFirstBubble ? KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.class : KeyVerificationIncomingRequestApprovalBubbleCell.class; } - else if (bubbleData.tag == RoomBubbleCellDataTagDeviceKeyVerificationRequest) + else if (bubbleData.tag == RoomBubbleCellDataTagKeyVerificationRequest) { - cellViewClass = KeyVerificationRequestStatusBubbleCell.class; + cellViewClass = bubbleData.isPaginationFirstBubble ? KeyVerificationRequestStatusWithPaginationTitleBubbleCell.class : KeyVerificationRequestStatusBubbleCell.class; } - else if (bubbleData.tag == RoomBubbleCellDataTagDeviceKeyVerificationConclusion) + else if (bubbleData.tag == RoomBubbleCellDataTagKeyVerificationConclusion) { - cellViewClass = KeyVerificationConclusionBubbleCell.class; + cellViewClass = bubbleData.isPaginationFirstBubble ? KeyVerificationConclusionWithPaginationTitleBubbleCell.class : KeyVerificationConclusionBubbleCell.class; } else if (bubbleData.tag == RoomBubbleCellDataTagMembership) { @@ -2271,6 +2275,30 @@ - (void)dataSource:(MXKDataSource *)dataSource didRecognizeAction:(NSString *)ac [self showContextualMenuForEvent:selectedEvent fromSingleTapGesture:YES cell:cell animated:YES]; } } + else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellKeyVerificationIncomingRequestAcceptPressed]) + { + NSString *eventId = userInfo[kMXKRoomBubbleCellEventIdKey]; + + RoomDataSource *roomDataSource = (RoomDataSource*)self.roomDataSource; + + [roomDataSource acceptVerificationRequestForEventId:eventId success:^{ + + } failure:^(NSError *error) { + [[AppDelegate theDelegate] showErrorAsAlert:error]; + }]; + } + else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellKeyVerificationIncomingRequestDeclinePressed]) + { + NSString *eventId = userInfo[kMXKRoomBubbleCellEventIdKey]; + + RoomDataSource *roomDataSource = (RoomDataSource*)self.roomDataSource; + + [roomDataSource declineVerificationRequestForEventId:eventId success:^{ + + } failure:^(NSError *error) { + [[AppDelegate theDelegate] showErrorAsAlert:error]; + }]; + } else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellTapOnAttachmentView]) { if (((MXKRoomBubbleTableViewCell*)cell).bubbleData.attachment.eventSentState == MXEventSentStateFailed) @@ -5319,8 +5347,36 @@ - (void)removeMXSessionStateChangeNotificationsListener // Copy action + BOOL isCopyActionEnabled = !attachment || attachment.type != MXKAttachmentTypeSticker; + + if (isCopyActionEnabled) + { + switch (event.eventType) { + case MXEventTypeRoomMessage: + { + NSString *messageType = event.content[@"msgtype"]; + + if ([messageType isEqualToString:kMXMessageTypeKeyVerificationRequest]) + { + isCopyActionEnabled = NO; + } + break; + } + case MXEventTypeKeyVerificationStart: + case MXEventTypeKeyVerificationAccept: + case MXEventTypeKeyVerificationKey: + case MXEventTypeKeyVerificationMac: + case MXEventTypeKeyVerificationDone: + case MXEventTypeKeyVerificationCancel: + isCopyActionEnabled = NO; + break; + default: + break; + } + } + RoomContextualMenuItem *copyMenuItem = [[RoomContextualMenuItem alloc] initWithMenuAction:RoomContextualMenuActionCopy]; - copyMenuItem.isEnabled = !attachment || attachment.type != MXKAttachmentTypeSticker; + copyMenuItem.isEnabled = isCopyActionEnabled; copyMenuItem.action = ^{ MXStrongifyAndReturnIfNil(self); From 0ad759c18e9197ddc7d87d43efeaa3196c84d02a Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Jan 2020 21:47:16 +0100 Subject: [PATCH 064/282] RoomDataSource: Handle RoomBubbleCellData key verification update. Handle incoming key verification approval. --- Riot/AppDelegate.h | 9 + Riot/AppDelegate.m | 26 ++ .../Modules/Room/DataSources/RoomDataSource.h | 22 ++ .../Modules/Room/DataSources/RoomDataSource.m | 253 ++++++++++++++++++ 4 files changed, 310 insertions(+) diff --git a/Riot/AppDelegate.h b/Riot/AppDelegate.h index 1ecb646da8..70984e2b9c 100644 --- a/Riot/AppDelegate.h +++ b/Riot/AppDelegate.h @@ -130,6 +130,15 @@ extern NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey; - (void)logoutSendingRequestServer:(BOOL)sendLogoutServerRequest completion:(void (^)(BOOL isLoggedOut))completion; +/** + Present incoming key verification request to accept. + + @param incomingKeyVerificationRequest The incoming key verification request. + @param The matrix session. + @return Indicate NO if the key verification screen could not be presented. + */ +- (BOOL)presentIncomingKeyVerificationRequest:(MXKeyVerificationRequest*)incomingKeyVerificationRequest + inSession:(MXSession*)session; #pragma mark - Matrix Accounts handling diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 517395fb02..df926b902e 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -4826,6 +4826,32 @@ - (void)checkPendingIncomingDeviceVerifications } } +- (BOOL)presentIncomingKeyVerificationRequest:(MXKeyVerificationRequest*)incomingKeyVerificationRequest + inSession:(MXSession*)session +{ + BOOL presented = NO; + + if (!deviceVerificationCoordinatorBridgePresenter) + { + NSLog(@"[AppDelegate] presentIncomingKeyVerificationRequest"); + + UIViewController *presentingViewController = self.window.rootViewController.presentedViewController ?: self.window.rootViewController; + + deviceVerificationCoordinatorBridgePresenter = [[DeviceVerificationCoordinatorBridgePresenter alloc] initWithSession:session]; + deviceVerificationCoordinatorBridgePresenter.delegate = self; + + [deviceVerificationCoordinatorBridgePresenter presentFrom:presentingViewController incomingKeyVerificationRequest:incomingKeyVerificationRequest animated:YES]; + + presented = YES; + } + else + { + NSLog(@"[AppDelegate][MXKeyVerification] presentIncomingDeviceVerification: Controller already presented."); + } + + return presented; +} + - (BOOL)presentIncomingDeviceVerification:(MXIncomingSASTransaction*)transaction inSession:(MXSession*)mxSession { NSLog(@"[AppDelegate][MXKeyVerification] presentIncomingDeviceVerification: %@", transaction); diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.h b/Riot/Modules/Room/DataSources/RoomDataSource.h index 1c7dfda5ac..fd01e5ad69 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.h +++ b/Riot/Modules/Room/DataSources/RoomDataSource.h @@ -62,4 +62,26 @@ success:(void (^)(NSString *eventId))success failure:(void (^)(NSError *error))failure; +/** + Accept incoming key verification request. + + @param eventId Event id associated to the key verification request event. + @param success A block object called when the operation succeeds. + @param failure A block object called when the operation fails. + */ +- (void)acceptVerificationRequestForEventId:(NSString*)eventId + success:(void(^)(void))success + failure:(void(^)(NSError*))failure; + +/** + Decline incoming key verification request. + + @param eventId Event id associated to the key verification request event. + @param success A block object called when the operation succeeds. + @param failure A block object called when the operation fails. + */ +- (void)declineVerificationRequestForEventId:(NSString*)eventId + success:(void(^)(void))success + failure:(void(^)(NSError*))failure; + @end diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m index 5b82ce4fee..280eef50d3 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -35,6 +35,15 @@ @interface RoomDataSource() id kThemeServiceDidChangeThemeNotificationObserver; } +// Observe key verification request changes +@property (nonatomic, weak) id keyVerificationRequestDidChangeNotificationObserver; + +// Observe key verification transaction changes +@property (nonatomic, weak) id deviceVerificationTransactionDidChangeNotificationObserver; + +// Timer used to debounce cells refresh +@property (nonatomic, strong) NSTimer *refreshCellsTimer; + @end @implementation RoomDataSource @@ -71,6 +80,9 @@ - (instancetype)initWithRoomId:(NSString *)roomId andMatrixSession:(MXSession *) [self reload]; }]; + + [self registerKeyVerificationRequestNotification]; + [self registerDeviceVerificationTransactionNotification]; } return self; } @@ -117,6 +129,16 @@ - (void)destroy kThemeServiceDidChangeThemeNotificationObserver = nil; } + if (self.keyVerificationRequestDidChangeNotificationObserver) + { + [[NSNotificationCenter defaultCenter] removeObserver:self.keyVerificationRequestDidChangeNotificationObserver]; + } + + if (self.deviceVerificationTransactionDidChangeNotificationObserver) + { + [[NSNotificationCenter defaultCenter] removeObserver:self.deviceVerificationTransactionDidChangeNotificationObserver]; + } + [super destroy]; } @@ -189,6 +211,8 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N { roomBubbleCellData.senderAvatarPlaceholder = [AvatarGenerator generateAvatarForMatrixItem:roomBubbleCellData.senderId withDisplayName:roomBubbleCellData.senderDisplayName]; } + + [self updateKeyVerificationIfNeededForRoomBubbleCellData:roomBubbleCellData]; UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath]; @@ -526,6 +550,173 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N return cell; } +- (RoomBubbleCellData*)roomBubbleCellDataForEventId:(NSString*)eventId +{ + id cellData = [self cellDataOfEventWithEventId:eventId]; + RoomBubbleCellData *roomBubbleCellData; + + if ([cellData isKindOfClass:RoomBubbleCellData.class]) + { + roomBubbleCellData = (RoomBubbleCellData*)cellData; + } + + return roomBubbleCellData; +} + +- (MXKeyVerificationRequest*)keyVerificationRequestFromEventId:(NSString*)eventId +{ + RoomBubbleCellData *roomBubbleCellData = [self roomBubbleCellDataForEventId:eventId]; + + return roomBubbleCellData.keyVerification.request; +} + +- (void)refreshCellsWithDelay +{ + if (self.refreshCellsTimer) + { + return; + } + + self.refreshCellsTimer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(refreshCellsTimerFired) userInfo:nil repeats:NO]; +} + +- (void)refreshCellsTimerFired +{ + [self refreshCells]; + self.refreshCellsTimer = nil; +} + +- (void)refreshCells +{ + if (self.delegate) + { + [self.delegate dataSource:self didCellChange:nil]; + } +} + +- (void)registerKeyVerificationRequestNotification +{ + self.keyVerificationRequestDidChangeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:MXKeyVerificationRequestDidChangeNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification *notification) + { + id notificationObject = notification.object; + + if ([notificationObject isKindOfClass:MXKeyVerificationByDMRequest.class]) + { + MXKeyVerificationByDMRequest *keyVerificationByDMRequest = (MXKeyVerificationByDMRequest*)notificationObject; + + if ([keyVerificationByDMRequest.roomId isEqualToString:self.roomId]) + { + RoomBubbleCellData *roomBubbleCellData = [self roomBubbleCellDataForEventId:keyVerificationByDMRequest.eventId]; + + roomBubbleCellData.isKeyVerificationOperationPending = NO; + roomBubbleCellData.keyVerification = nil; + + if (roomBubbleCellData) + { + [self refreshCellsWithDelay]; + } + } + } + }]; +} + +- (void)registerDeviceVerificationTransactionNotification +{ + self.deviceVerificationTransactionDidChangeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:MXDeviceVerificationTransactionDidChangeNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification *notification) + { + MXDeviceVerificationTransaction *deviceVerificationTransaction = (MXDeviceVerificationTransaction*)notification.object; + + if ([deviceVerificationTransaction.dmRoomId isEqualToString:self.roomId]) + { + RoomBubbleCellData *roomBubbleCellData = [self roomBubbleCellDataForEventId:deviceVerificationTransaction.dmEventId]; + + roomBubbleCellData.isKeyVerificationOperationPending = NO; + roomBubbleCellData.keyVerification = nil; + + if (roomBubbleCellData) + { + [self refreshCellsWithDelay]; + } + } + }]; +} + +- (BOOL)shouldFetchKeyVerificationForEvent:(MXEvent*)event +{ + if (!event) + { + return NO; + } + + BOOL shouldFetchKeyVerification = NO; + + switch (event.eventType) + { + case MXEventTypeKeyVerificationDone: + case MXEventTypeKeyVerificationCancel: + shouldFetchKeyVerification = YES; + break; + case MXEventTypeRoomMessage: + { + NSString *msgType = event.content[@"msgtype"]; + + if ([msgType isEqualToString:kMXMessageTypeKeyVerificationRequest]) + { + shouldFetchKeyVerification = YES; + } + } + break; + default: + break; + } + + return shouldFetchKeyVerification; +} + +- (void)updateKeyVerificationIfNeededForRoomBubbleCellData:(RoomBubbleCellData*)bubbleCellData +{ + MXEvent *event = bubbleCellData.getFirstBubbleComponentWithDisplay.event; + + if (![self shouldFetchKeyVerificationForEvent:event]) + { + return; + } + + if (bubbleCellData.keyVerification != nil || bubbleCellData.isKeyVerificationOperationPending) + { + // Key verification already fetched or request is pending do nothing + return; + } + + __block MXHTTPOperation *operation = [self.mxSession.crypto.deviceVerificationManager keyVerificationFromKeyVerificationEvent:event + success:^(MXKeyVerification * _Nonnull keyVerification) + { + BOOL shouldRefreshCells = bubbleCellData.isKeyVerificationOperationPending || bubbleCellData.keyVerification == nil; + + bubbleCellData.keyVerification = keyVerification; + bubbleCellData.isKeyVerificationOperationPending = NO; + + if (shouldRefreshCells) + { + [self refreshCellsWithDelay]; + } + + } failure:^(NSError * _Nonnull error) { + + NSLog(@"[RoomDataSource] updateKeyVerificationIfNeededForRoomBubbleCellData; keyVerificationFromKeyVerificationEvent fails with error: %@", error); + + bubbleCellData.isKeyVerificationOperationPending = NO; + }]; + + bubbleCellData.isKeyVerificationOperationPending = !operation; +} + #pragma mark - - (void)setSelectedEventId:(NSString *)selectedEventId @@ -577,6 +768,68 @@ - (void)sendVideo:(NSURL*)videoLocalURL [self sendVideo:videoLocalURL withThumbnail:videoThumbnail success:success failure:failure]; } +- (void)acceptVerificationRequestForEventId:(NSString*)eventId success:(void(^)(void))success failure:(void(^)(NSError*))failure +{ + MXKeyVerificationRequest *keyVerificationRequest = [self keyVerificationRequestFromEventId:eventId]; + + if (!keyVerificationRequest) + { + NSError *error; + + if (failure) + { + failure(error); + } + return; + } + + [[AppDelegate theDelegate] presentIncomingKeyVerificationRequest:keyVerificationRequest inSession:self.mxSession]; + + if (success) + { + success(); + } +} + +- (void)declineVerificationRequestForEventId:(NSString*)eventId success:(void(^)(void))success failure:(void(^)(NSError*))failure +{ + MXKeyVerificationRequest *keyVerificationRequest = [self keyVerificationRequestFromEventId:eventId]; + + if (!keyVerificationRequest) + { + NSError *error; + + if (failure) + { + failure(error); + } + return; + } + + RoomBubbleCellData *roomBubbleCellData = [self roomBubbleCellDataForEventId:eventId]; + roomBubbleCellData.isKeyVerificationOperationPending = YES; + + [self refreshCells]; + + [keyVerificationRequest cancelWithCancelCode:MXTransactionCancelCode.user success:^{ + + // roomBubbleCellData.isKeyVerificationOperationPending will be set to NO by MXKeyVerificationRequestDidChangeNotification notification + + if (success) + { + success(); + } + + } failure:^(NSError * _Nonnull error) { + + roomBubbleCellData.isKeyVerificationOperationPending = NO; + + if (failure) + { + failure(error); + } + }]; +} #pragma - Accessibility From 972a1174c891a561a0134567bb219bb1d408301f Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Jan 2020 21:51:05 +0100 Subject: [PATCH 065/282] DeviceVerificationCoordinator: Handle incoming key verification to accept. --- .../DeviceVerificationCoordinator.swift | 40 +++++- ...rificationCoordinatorBridgePresenter.swift | 12 ++ ...ceVerificationDataLoadingCoordinator.swift | 17 ++- ...rificationDataLoadingCoordinatorType.swift | 1 + ...erificationDataLoadingViewController.swift | 32 ++++- ...viceVerificationDataLoadingViewModel.swift | 123 +++++++++++++++--- ...VerificationDataLoadingViewModelType.swift | 1 + 7 files changed, 197 insertions(+), 29 deletions(-) diff --git a/Riot/Modules/DeviceVerification/DeviceVerificationCoordinator.swift b/Riot/Modules/DeviceVerification/DeviceVerificationCoordinator.swift index 3687da88ae..84dca96669 100644 --- a/Riot/Modules/DeviceVerification/DeviceVerificationCoordinator.swift +++ b/Riot/Modules/DeviceVerification/DeviceVerificationCoordinator.swift @@ -31,6 +31,7 @@ final class DeviceVerificationCoordinator: DeviceVerificationCoordinatorType { private let otherDeviceId: String private var incomingTransaction: MXIncomingSASTransaction? + private var incomingKeyVerificationRequest: MXKeyVerificationRequest? // MARK: Public @@ -66,12 +67,29 @@ final class DeviceVerificationCoordinator: DeviceVerificationCoordinatorType { self.incomingTransaction = incomingTransaction } + /// Contrustor to manage an incoming SAS device verification transaction + /// + /// - Parameters: + /// - session: the MXSession + /// - incomingKeyVerificationRequest: An existing incoming key verification request to accept + convenience init(session: MXSession, incomingKeyVerificationRequest: MXKeyVerificationRequest) { + self.init(session: session, otherUserId: incomingKeyVerificationRequest.sender, otherDeviceId: incomingKeyVerificationRequest.fromDevice) + self.incomingKeyVerificationRequest = incomingKeyVerificationRequest + } + // MARK: - Public methods func start() { - let rootCoordinator = self.createDataLoadingScreenCoordinator() + let rootCoordinator: Coordinator & Presentable + + if let incomingKeyVerificationRequest = self.incomingKeyVerificationRequest { + rootCoordinator = self.createDataLoadingScreenCoordinator(with: incomingKeyVerificationRequest) + } else { + rootCoordinator = self.createDataLoadingScreenCoordinator() + } + rootCoordinator.start() - + self.add(childCoordinator: rootCoordinator) self.navigationRouter.setRootModule(rootCoordinator) { [weak self] in self?.remove(childCoordinator: rootCoordinator) @@ -91,6 +109,14 @@ final class DeviceVerificationCoordinator: DeviceVerificationCoordinatorType { return coordinator } + + private func createDataLoadingScreenCoordinator(with keyVerificationRequest: MXKeyVerificationRequest) -> DeviceVerificationDataLoadingCoordinator { + let coordinator = DeviceVerificationDataLoadingCoordinator(incomingKeyVerificationRequest: keyVerificationRequest) + coordinator.delegate = self + coordinator.start() + + return coordinator + } private func showStart(otherUser: MXUser, otherDevice: MXDeviceInfo) { let coordinator = DeviceVerificationStartCoordinator(session: self.session, otherUser: otherUser, otherDevice: otherDevice) @@ -141,6 +167,16 @@ extension DeviceVerificationCoordinator: DeviceVerificationDataLoadingCoordinato self.showStart(otherUser: user, otherDevice: device) } } + + func deviceVerificationDataLoadingCoordinator(_ coordinator: DeviceVerificationDataLoadingCoordinatorType, didAcceptKeyVerificationRequestWithTransaction transaction: MXDeviceVerificationTransaction) { + + if let sasTransaction = transaction as? MXSASTransaction { + self.showVerify(transaction: sasTransaction, animated: true) + } else { + NSLog("[DeviceVerificationCoordinator] Transaction \(transaction) is not supported") + self.delegate?.deviceVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId) + } + } func deviceVerificationDataLoadingCoordinatorDidCancel(_ coordinator: DeviceVerificationDataLoadingCoordinatorType) { self.delegate?.deviceVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId) diff --git a/Riot/Modules/DeviceVerification/DeviceVerificationCoordinatorBridgePresenter.swift b/Riot/Modules/DeviceVerification/DeviceVerificationCoordinatorBridgePresenter.swift index c809533a38..bdd170fb6a 100644 --- a/Riot/Modules/DeviceVerification/DeviceVerificationCoordinatorBridgePresenter.swift +++ b/Riot/Modules/DeviceVerification/DeviceVerificationCoordinatorBridgePresenter.swift @@ -73,6 +73,18 @@ final class DeviceVerificationCoordinatorBridgePresenter: NSObject { viewController.present(deviceVerificationCoordinator.toPresentable(), animated: animated, completion: nil) deviceVerificationCoordinator.start() + self.coordinator = deviceVerificationCoordinator + } + + func present(from viewController: UIViewController, incomingKeyVerificationRequest: MXKeyVerificationRequest, animated: Bool) { + + NSLog("[DeviceVerificationCoordinatorBridgePresenter] Present incoming key verification request from \(viewController)") + + let deviceVerificationCoordinator = DeviceVerificationCoordinator(session: self.session, incomingKeyVerificationRequest: incomingKeyVerificationRequest) + deviceVerificationCoordinator.delegate = self + viewController.present(deviceVerificationCoordinator.toPresentable(), animated: animated, completion: nil) + deviceVerificationCoordinator.start() + self.coordinator = deviceVerificationCoordinator } diff --git a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingCoordinator.swift b/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingCoordinator.swift index ece7185af9..9e4e9309ea 100644 --- a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingCoordinator.swift +++ b/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingCoordinator.swift @@ -25,7 +25,6 @@ final class DeviceVerificationDataLoadingCoordinator: DeviceVerificationDataLoad // MARK: Private - private let session: MXSession private var deviceVerificationDataLoadingViewModel: DeviceVerificationDataLoadingViewModelType private let deviceVerificationDataLoadingViewController: DeviceVerificationDataLoadingViewController @@ -39,9 +38,14 @@ final class DeviceVerificationDataLoadingCoordinator: DeviceVerificationDataLoad // MARK: - Setup init(session: MXSession, otherUserId: String, otherDeviceId: String) { - self.session = session - - let deviceVerificationDataLoadingViewModel = DeviceVerificationDataLoadingViewModel(session: self.session, otherUserId: otherUserId, otherDeviceId: otherDeviceId) + let deviceVerificationDataLoadingViewModel = DeviceVerificationDataLoadingViewModel(session: session, otherUserId: otherUserId, otherDeviceId: otherDeviceId) + let deviceVerificationDataLoadingViewController = DeviceVerificationDataLoadingViewController.instantiate(with: deviceVerificationDataLoadingViewModel) + self.deviceVerificationDataLoadingViewModel = deviceVerificationDataLoadingViewModel + self.deviceVerificationDataLoadingViewController = deviceVerificationDataLoadingViewController + } + + init(incomingKeyVerificationRequest: MXKeyVerificationRequest) { + let deviceVerificationDataLoadingViewModel = DeviceVerificationDataLoadingViewModel(keyVerificationRequest: incomingKeyVerificationRequest) let deviceVerificationDataLoadingViewController = DeviceVerificationDataLoadingViewController.instantiate(with: deviceVerificationDataLoadingViewModel) self.deviceVerificationDataLoadingViewModel = deviceVerificationDataLoadingViewModel self.deviceVerificationDataLoadingViewController = deviceVerificationDataLoadingViewController @@ -60,6 +64,11 @@ final class DeviceVerificationDataLoadingCoordinator: DeviceVerificationDataLoad // MARK: - DeviceVerificationDataLoadingViewModelCoordinatorDelegate extension DeviceVerificationDataLoadingCoordinator: DeviceVerificationDataLoadingViewModelCoordinatorDelegate { + + func deviceVerificationDataLoadingViewModel(_ viewModel: DeviceVerificationDataLoadingViewModelType, didAcceptKeyVerificationWithTransaction transaction: MXDeviceVerificationTransaction) { + self.delegate?.deviceVerificationDataLoadingCoordinator(self, didAcceptKeyVerificationRequestWithTransaction: transaction) + } + func deviceVerificationDataLoadingViewModel(_ viewModel: DeviceVerificationDataLoadingViewModelType, didLoadUser user: MXUser, device: MXDeviceInfo) { self.delegate?.deviceVerificationDataLoadingCoordinator(self, didLoadUser: user, device: device) } diff --git a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingCoordinatorType.swift b/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingCoordinatorType.swift index c70b8cc50f..9f8ef89766 100644 --- a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingCoordinatorType.swift +++ b/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingCoordinatorType.swift @@ -20,6 +20,7 @@ import Foundation protocol DeviceVerificationDataLoadingCoordinatorDelegate: class { func deviceVerificationDataLoadingCoordinator(_ coordinator: DeviceVerificationDataLoadingCoordinatorType, didLoadUser user: MXUser, device: MXDeviceInfo) + func deviceVerificationDataLoadingCoordinator(_ coordinator: DeviceVerificationDataLoadingCoordinatorType, didAcceptKeyVerificationRequestWithTransaction transaction: MXDeviceVerificationTransaction) func deviceVerificationDataLoadingCoordinatorDidCancel(_ coordinator: DeviceVerificationDataLoadingCoordinatorType) } diff --git a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewController.swift b/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewController.swift index 80ae30af7a..a21cc9f310 100644 --- a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewController.swift +++ b/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewController.swift @@ -112,9 +112,35 @@ final class DeviceVerificationDataLoadingViewController: UIViewController { } private func render(error: Error) { - self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: { - self.viewModel.process(viewAction: .cancel) - }) + + var shouldDisplayError = true + var message: String? + + switch error { + case DeviceVerificationDataLoadingViewModelError.transactionCancelled: + message = VectorL10n.deviceVerificationCancelled + case DeviceVerificationDataLoadingViewModelError.transactionCancelledByMe(reason: let reason): + if reason.value != MXTransactionCancelCode.user().value { + message = VectorL10n.deviceVerificationCancelledByMe(reason.humanReadable) + } else { + shouldDisplayError = false + } + default: + break + } + + if shouldDisplayError { + + let completion = { + self.viewModel.process(viewAction: .cancel) + } + + if let message = message { + self.errorPresenter.presentError(from: self, title: "", message: message, animated: true, handler: completion) + } else { + self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: completion) + } + } } private func renderError(message: String) { diff --git a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewModel.swift b/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewModel.swift index ed9ddb2fa3..47c7799a98 100644 --- a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewModel.swift +++ b/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewModel.swift @@ -20,6 +20,8 @@ import Foundation enum DeviceVerificationDataLoadingViewModelError: Error { case unknown + case transactionCancelled + case transactionCancelledByMe(reason: MXTransactionCancelCode) } final class DeviceVerificationDataLoadingViewModel: DeviceVerificationDataLoadingViewModelType { @@ -28,9 +30,13 @@ final class DeviceVerificationDataLoadingViewModel: DeviceVerificationDataLoadin // MARK: Private - private let session: MXSession - private let otherUserId: String - private let otherDeviceId: String + private let session: MXSession? + private let otherUserId: String? + private let otherDeviceId: String? + + private let keyVerificationRequest: MXKeyVerificationRequest? + + private var currentOperation: MXHTTPOperation? // MARK: Public @@ -43,9 +49,18 @@ final class DeviceVerificationDataLoadingViewModel: DeviceVerificationDataLoadin self.session = session self.otherUserId = otherUserId self.otherDeviceId = otherDeviceId + self.keyVerificationRequest = nil + } + + init(keyVerificationRequest: MXKeyVerificationRequest) { + self.session = nil + self.otherUserId = nil + self.otherDeviceId = nil + self.keyVerificationRequest = keyVerificationRequest } deinit { + self.currentOperation?.cancel() } // MARK: - Public @@ -60,40 +75,74 @@ final class DeviceVerificationDataLoadingViewModel: DeviceVerificationDataLoadin } // MARK: - Private - + private func loadData() { - guard let crypto = self.session.crypto else { + if let keyVerificationRequest = self.keyVerificationRequest { + self.acceptKeyVerificationRequest(keyVerificationRequest) + } else { + self.downloadOtherDeviceKeys() + } + } + + private func acceptKeyVerificationRequest(_ keyVerificationRequest: MXKeyVerificationRequest) { + + self.update(viewState: .loading) + + keyVerificationRequest.accept(withMethod: MXKeyVerificationMethodSAS, success: { [weak self] (deviceVerificationTransaction) in + guard let self = self else { + return + } + + if let outgoingSASTransaction = deviceVerificationTransaction as? MXOutgoingSASTransaction { + self.registerTransactionDidStateChangeNotification(transaction: outgoingSASTransaction) + } else { + self.update(viewState: .error(DeviceVerificationDataLoadingViewModelError.unknown)) + } + + }, failure: { [weak self] (error) in + guard let self = self else { + return + } + self.update(viewState: .error(error)) + }) + } + + private func downloadOtherDeviceKeys() { + guard let session = self.session, + let crypto = session.crypto, + let otherUserId = self.otherUserId, + let otherDeviceId = self.otherDeviceId else { self.update(viewState: .errorMessage(VectorL10n.deviceVerificationErrorCannotLoadDevice)) NSLog("[DeviceVerificationDataLoadingViewModel] Error session.crypto is nil") return } - - if let otherUser = self.session.user(withUserId: otherUserId) { + + if let otherUser = session.user(withUserId: otherUserId) { self.update(viewState: .loading) - crypto.downloadKeys([self.otherUserId], forceDownload: false, success: { [weak self] (usersDevicesMap) in + self.currentOperation = crypto.downloadKeys([otherUserId], forceDownload: false, success: { [weak self] (usersDevicesMap) in guard let sself = self else { return } - - if let otherDevice = usersDevicesMap?.object(forDevice: sself.otherDeviceId, forUser: sself.otherUserId) { + + if let otherDevice = usersDevicesMap?.object(forDevice: otherDeviceId, forUser: otherUserId) { sself.update(viewState: .loaded) sself.coordinatorDelegate?.deviceVerificationDataLoadingViewModel(sself, didLoadUser: otherUser, device: otherDevice) } else { - sself.update(viewState: .errorMessage(VectorL10n.deviceVerificationErrorCannotLoadDevice)) + sself.update(viewState: .errorMessage(VectorL10n.deviceVerificationErrorCannotLoadDevice)) } - - }, failure: { [weak self] (error) in - guard let sself = self else { - return - } - - let finalError = error ?? DeviceVerificationDataLoadingViewModelError.unknown - sself.update(viewState: .error(finalError)) + }, failure: { [weak self] (error) in + guard let sself = self else { + return + } + + let finalError = error ?? DeviceVerificationDataLoadingViewModelError.unknown + + sself.update(viewState: .error(finalError)) }) - + } else { self.update(viewState: .errorMessage(VectorL10n.deviceVerificationErrorCannotLoadDevice)) } @@ -102,4 +151,38 @@ final class DeviceVerificationDataLoadingViewModel: DeviceVerificationDataLoadin private func update(viewState: DeviceVerificationDataLoadingViewState) { self.viewDelegate?.deviceVerificationDataLoadingViewModel(self, didUpdateViewState: viewState) } + + // MARK: MXDeviceVerificationTransactionDidChange + + private func registerTransactionDidStateChangeNotification(transaction: MXOutgoingSASTransaction) { + NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: NSNotification.Name.MXDeviceVerificationTransactionDidChange, object: transaction) + } + + private func unregisterTransactionDidStateChangeNotification() { + NotificationCenter.default.removeObserver(self, name: .MXDeviceVerificationTransactionDidChange, object: nil) + } + + @objc private func transactionDidStateChange(notification: Notification) { + guard let transaction = notification.object as? MXOutgoingSASTransaction else { + return + } + + switch transaction.state { + case MXSASTransactionStateShowSAS: + self.unregisterTransactionDidStateChangeNotification() + self.update(viewState: .loaded) + self.coordinatorDelegate?.deviceVerificationDataLoadingViewModel(self, didAcceptKeyVerificationWithTransaction: transaction) + case MXSASTransactionStateCancelled: + self.unregisterTransactionDidStateChangeNotification() + self.update(viewState: .error(DeviceVerificationDataLoadingViewModelError.transactionCancelled)) + case MXSASTransactionStateCancelledByMe: + guard let reason = transaction.reasonCancelCode else { + return + } + self.unregisterTransactionDidStateChangeNotification() + self.update(viewState: .error(DeviceVerificationDataLoadingViewModelError.transactionCancelledByMe(reason: reason))) + default: + break + } + } } diff --git a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewModelType.swift b/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewModelType.swift index 0121c00289..7808df654b 100644 --- a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewModelType.swift +++ b/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewModelType.swift @@ -24,6 +24,7 @@ protocol DeviceVerificationDataLoadingViewModelViewDelegate: class { protocol DeviceVerificationDataLoadingViewModelCoordinatorDelegate: class { func deviceVerificationDataLoadingViewModel(_ viewModel: DeviceVerificationDataLoadingViewModelType, didLoadUser user: MXUser, device: MXDeviceInfo) + func deviceVerificationDataLoadingViewModel(_ viewModel: DeviceVerificationDataLoadingViewModelType, didAcceptKeyVerificationWithTransaction transaction: MXDeviceVerificationTransaction) func deviceVerificationDataLoadingViewModelDidCancel(_ viewModel: DeviceVerificationDataLoadingViewModelType) } From f5ca4d4bf80969738a889b0b48037df813a7fd9a Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Jan 2020 21:52:02 +0100 Subject: [PATCH 066/282] Update pbxproj --- Riot.xcodeproj/project.pbxproj | 88 ++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index fd3d454aa7..fb93d2e5f7 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -129,6 +129,12 @@ B105778B221304FA00334B1E /* KeyBackupSetupSuccessFromPassphraseViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B105778A221304FA00334B1E /* KeyBackupSetupSuccessFromPassphraseViewController.storyboard */; }; B105778D2213051E00334B1E /* KeyBackupSetupSuccessFromRecoveryKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B105778C2213051E00334B1E /* KeyBackupSetupSuccessFromRecoveryKeyViewController.swift */; }; B105778F2213052A00334B1E /* KeyBackupSetupSuccessFromRecoveryKeyViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B105778E2213052A00334B1E /* KeyBackupSetupSuccessFromRecoveryKeyViewController.storyboard */; }; + B108931F23AB80EF00802670 /* KeyVerificationIncomingRequestApprovalBubbleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B108931E23AB80EF00802670 /* KeyVerificationIncomingRequestApprovalBubbleCell.swift */; }; + B108932123AB8D7D00802670 /* KeyVerificationIncomingRequestApprovalViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B108932023AB8D7D00802670 /* KeyVerificationIncomingRequestApprovalViewData.swift */; }; + B108932323AB908A00802670 /* KeyVerificationRequestStatusViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B108932223AB908A00802670 /* KeyVerificationRequestStatusViewData.swift */; }; + B108932523AB93A200802670 /* KeyVerificationConclusionViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B108932423AB93A200802670 /* KeyVerificationConclusionViewData.swift */; }; + B108932823ABEE6800802670 /* BubbleCellReadReceiptsDisplayable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B108932723ABEE6700802670 /* BubbleCellReadReceiptsDisplayable.swift */; }; + B108932A23ACBA0B00802670 /* SizingViewHeight.swift in Sources */ = {isa = PBXBuildFile; fileRef = B108932923ACBA0B00802670 /* SizingViewHeight.swift */; }; B1098BDF21ECE09F000DDA48 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1098BDA21ECE09E000DDA48 /* Strings.swift */; }; B1098BE121ECE09F000DDA48 /* Images.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1098BDC21ECE09E000DDA48 /* Images.swift */; }; B1098BE321ECE09F000DDA48 /* RiotDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1098BDE21ECE09E000DDA48 /* RiotDefaults.swift */; }; @@ -175,6 +181,11 @@ B139C22121FE5D9D00BB68EC /* KeyBackupRecoverFromPassphraseViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B139C22021FE5D9D00BB68EC /* KeyBackupRecoverFromPassphraseViewState.swift */; }; B139C22321FF01B200BB68EC /* KeyBackupRecoverFromPassphraseCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B139C22221FF01B200BB68EC /* KeyBackupRecoverFromPassphraseCoordinatorType.swift */; }; B139C22521FF01C100BB68EC /* KeyBackupRecoverFromPassphraseCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B139C22421FF01C100BB68EC /* KeyBackupRecoverFromPassphraseCoordinator.swift */; }; + B14084C623BF76890010F692 /* BubbleCellContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14084C523BF76890010F692 /* BubbleCellContentView.swift */; }; + B14084C823BF76CB0010F692 /* BubbleCellContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B14084C723BF76CB0010F692 /* BubbleCellContentView.xib */; }; + B14084CA23BF89310010F692 /* KeyVerificationRequestStatusWithPaginationTitleBubbleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14084C923BF89310010F692 /* KeyVerificationRequestStatusWithPaginationTitleBubbleCell.swift */; }; + B14084CC23BF9DE90010F692 /* KeyVerificationConclusionWithPaginationTitleBubbleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14084CB23BF9DE90010F692 /* KeyVerificationConclusionWithPaginationTitleBubbleCell.swift */; }; + B14084CE23BFA0990010F692 /* KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14084CD23BFA0990010F692 /* KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.swift */; }; B140B4A221F87F7100E3F5FE /* OperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = B140B4A121F87F7100E3F5FE /* OperationQueue.swift */; }; B140B4A621F89E7600E3F5FE /* KeyBackupSetupCoordinatorBridgePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B140B4A521F89E7600E3F5FE /* KeyBackupSetupCoordinatorBridgePresenter.swift */; }; B140B4A821F8AB4600E3F5FE /* KeyBackupRecoverCoordinatorBridgePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B140B4A721F8AB4600E3F5FE /* KeyBackupRecoverCoordinatorBridgePresenter.swift */; }; @@ -531,6 +542,11 @@ B1C45A8A232A8C2600165425 /* SettingsIdentityServerCoordinatorBridgePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C45A81232A8C2600165425 /* SettingsIdentityServerCoordinatorBridgePresenter.swift */; }; B1C45A8B232A8C2600165425 /* SettingsIdentityServerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C45A82232A8C2600165425 /* SettingsIdentityServerViewModel.swift */; }; B1C45A8C232A8C2600165425 /* SettingsIdentityServerViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C45A83232A8C2600165425 /* SettingsIdentityServerViewAction.swift */; }; + B1C543A4239E98E400DCA1FA /* KeyVerificationCellInnerContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C543A3239E98E400DCA1FA /* KeyVerificationCellInnerContentView.swift */; }; + B1C543A6239E999700DCA1FA /* KeyVerificationCellInnerContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1C543A5239E999700DCA1FA /* KeyVerificationCellInnerContentView.xib */; }; + B1C543AE23A286A000DCA1FA /* KeyVerificationRequestStatusBubbleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C543AD23A286A000DCA1FA /* KeyVerificationRequestStatusBubbleCell.swift */; }; + B1C543B023A2871300DCA1FA /* KeyVerificationBaseBubbleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C543AF23A2871300DCA1FA /* KeyVerificationBaseBubbleCell.swift */; }; + B1C543B223A2913F00DCA1FA /* KeyVerificationConclusionBubbleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C543B123A2913F00DCA1FA /* KeyVerificationConclusionBubbleCell.swift */; }; B1C562CA2289C2690037F12A /* UIGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C562C92289C2690037F12A /* UIGestureRecognizer.swift */; }; B1C562CC228AB3510037F12A /* UIStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C562CB228AB3510037F12A /* UIStackView.swift */; }; B1C562D9228C0B760037F12A /* RoomContextualMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C562D8228C0B760037F12A /* RoomContextualMenuItem.swift */; }; @@ -806,6 +822,12 @@ B105778A221304FA00334B1E /* KeyBackupSetupSuccessFromPassphraseViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = KeyBackupSetupSuccessFromPassphraseViewController.storyboard; sourceTree = ""; }; B105778C2213051E00334B1E /* KeyBackupSetupSuccessFromRecoveryKeyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupSetupSuccessFromRecoveryKeyViewController.swift; sourceTree = ""; }; B105778E2213052A00334B1E /* KeyBackupSetupSuccessFromRecoveryKeyViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = KeyBackupSetupSuccessFromRecoveryKeyViewController.storyboard; sourceTree = ""; }; + B108931E23AB80EF00802670 /* KeyVerificationIncomingRequestApprovalBubbleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationIncomingRequestApprovalBubbleCell.swift; sourceTree = ""; }; + B108932023AB8D7D00802670 /* KeyVerificationIncomingRequestApprovalViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationIncomingRequestApprovalViewData.swift; sourceTree = ""; }; + B108932223AB908A00802670 /* KeyVerificationRequestStatusViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationRequestStatusViewData.swift; sourceTree = ""; }; + B108932423AB93A200802670 /* KeyVerificationConclusionViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationConclusionViewData.swift; sourceTree = ""; }; + B108932723ABEE6700802670 /* BubbleCellReadReceiptsDisplayable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BubbleCellReadReceiptsDisplayable.swift; sourceTree = ""; }; + B108932923ACBA0B00802670 /* SizingViewHeight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SizingViewHeight.swift; sourceTree = ""; }; B1098BDA21ECE09E000DDA48 /* Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = ""; }; B1098BDC21ECE09E000DDA48 /* Images.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Images.swift; sourceTree = ""; }; B1098BDE21ECE09E000DDA48 /* RiotDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RiotDefaults.swift; sourceTree = ""; }; @@ -852,6 +874,11 @@ B139C22021FE5D9D00BB68EC /* KeyBackupRecoverFromPassphraseViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewState.swift; sourceTree = ""; }; B139C22221FF01B200BB68EC /* KeyBackupRecoverFromPassphraseCoordinatorType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseCoordinatorType.swift; sourceTree = ""; }; B139C22421FF01C100BB68EC /* KeyBackupRecoverFromPassphraseCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseCoordinator.swift; sourceTree = ""; }; + B14084C523BF76890010F692 /* BubbleCellContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BubbleCellContentView.swift; sourceTree = ""; }; + B14084C723BF76CB0010F692 /* BubbleCellContentView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BubbleCellContentView.xib; sourceTree = ""; }; + B14084C923BF89310010F692 /* KeyVerificationRequestStatusWithPaginationTitleBubbleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationRequestStatusWithPaginationTitleBubbleCell.swift; sourceTree = ""; }; + B14084CB23BF9DE90010F692 /* KeyVerificationConclusionWithPaginationTitleBubbleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationConclusionWithPaginationTitleBubbleCell.swift; sourceTree = ""; }; + B14084CD23BFA0990010F692 /* KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.swift; sourceTree = ""; }; B140B4A121F87F7100E3F5FE /* OperationQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationQueue.swift; sourceTree = ""; }; B140B4A521F89E7600E3F5FE /* KeyBackupSetupCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupSetupCoordinatorBridgePresenter.swift; sourceTree = ""; }; B140B4A721F8AB4600E3F5FE /* KeyBackupRecoverCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverCoordinatorBridgePresenter.swift; sourceTree = ""; }; @@ -1393,6 +1420,11 @@ B1C45A81232A8C2600165425 /* SettingsIdentityServerCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsIdentityServerCoordinatorBridgePresenter.swift; sourceTree = ""; }; B1C45A82232A8C2600165425 /* SettingsIdentityServerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsIdentityServerViewModel.swift; sourceTree = ""; }; B1C45A83232A8C2600165425 /* SettingsIdentityServerViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsIdentityServerViewAction.swift; sourceTree = ""; }; + B1C543A3239E98E400DCA1FA /* KeyVerificationCellInnerContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationCellInnerContentView.swift; sourceTree = ""; }; + B1C543A5239E999700DCA1FA /* KeyVerificationCellInnerContentView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = KeyVerificationCellInnerContentView.xib; sourceTree = ""; }; + B1C543AD23A286A000DCA1FA /* KeyVerificationRequestStatusBubbleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationRequestStatusBubbleCell.swift; sourceTree = ""; }; + B1C543AF23A2871300DCA1FA /* KeyVerificationBaseBubbleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationBaseBubbleCell.swift; sourceTree = ""; }; + B1C543B123A2913F00DCA1FA /* KeyVerificationConclusionBubbleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationConclusionBubbleCell.swift; sourceTree = ""; }; B1C562C92289C2690037F12A /* UIGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIGestureRecognizer.swift; sourceTree = ""; }; B1C562CB228AB3510037F12A /* UIStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIStackView.swift; sourceTree = ""; }; B1C562D8228C0B760037F12A /* RoomContextualMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomContextualMenuItem.swift; sourceTree = ""; }; @@ -1911,6 +1943,16 @@ path = Success; sourceTree = ""; }; + B108932623ABE82C00802670 /* BaseContentViews */ = { + isa = PBXGroup; + children = ( + B108932723ABEE6700802670 /* BubbleCellReadReceiptsDisplayable.swift */, + B14084C523BF76890010F692 /* BubbleCellContentView.swift */, + B14084C723BF76CB0010F692 /* BubbleCellContentView.xib */, + ); + path = BaseContentViews; + sourceTree = ""; + }; B1098BD921ECE09E000DDA48 /* Generated */ = { isa = PBXGroup; children = ( @@ -2395,6 +2437,33 @@ path = Riot/Modules/Common/CollectionView; sourceTree = SOURCE_ROOT; }; + B1A12C64239AB74500AA2B86 /* CrossSigning */ = { + isa = PBXGroup; + children = ( + ); + path = CrossSigning; + sourceTree = ""; + }; + B1A12C65239ABBDB00AA2B86 /* KeyVerification */ = { + isa = PBXGroup; + children = ( + B108932923ACBA0B00802670 /* SizingViewHeight.swift */, + B1C543A3239E98E400DCA1FA /* KeyVerificationCellInnerContentView.swift */, + B1C543A5239E999700DCA1FA /* KeyVerificationCellInnerContentView.xib */, + B1C543AF23A2871300DCA1FA /* KeyVerificationBaseBubbleCell.swift */, + B108932023AB8D7D00802670 /* KeyVerificationIncomingRequestApprovalViewData.swift */, + B108931E23AB80EF00802670 /* KeyVerificationIncomingRequestApprovalBubbleCell.swift */, + B14084CD23BFA0990010F692 /* KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.swift */, + B108932223AB908A00802670 /* KeyVerificationRequestStatusViewData.swift */, + B1C543AD23A286A000DCA1FA /* KeyVerificationRequestStatusBubbleCell.swift */, + B14084C923BF89310010F692 /* KeyVerificationRequestStatusWithPaginationTitleBubbleCell.swift */, + B108932423AB93A200802670 /* KeyVerificationConclusionViewData.swift */, + B1C543B123A2913F00DCA1FA /* KeyVerificationConclusionBubbleCell.swift */, + B14084CB23BF9DE90010F692 /* KeyVerificationConclusionWithPaginationTitleBubbleCell.swift */, + ); + path = KeyVerification; + sourceTree = ""; + }; B1A6C10523881ECB002882FD /* SlidingModal */ = { isa = PBXGroup; children = ( @@ -2413,6 +2482,7 @@ B1B5567620EE6C4C00210D55 /* Modules */ = { isa = PBXGroup; children = ( + B1A12C64239AB74500AA2B86 /* CrossSigning */, B1A6C10523881ECB002882FD /* SlidingModal */, 32DB556722FDADE50016329E /* ServiceTerms */, 3232AB94225730E100AD6A5C /* DeviceVerification */, @@ -3249,6 +3319,8 @@ B1B5583F20EF768E00210D55 /* BubbleCells */ = { isa = PBXGroup; children = ( + B108932623ABE82C00802670 /* BaseContentViews */, + B1A12C65239ABBDB00AA2B86 /* KeyVerification */, B1B5584220EF768E00210D55 /* Encryption */, B1B5589220EF768E00210D55 /* RoomEmptyBubbleCell.h */, B1B558B620EF768E00210D55 /* RoomEmptyBubbleCell.m */, @@ -4176,6 +4248,7 @@ B1B558FB20EF768F00210D55 /* RoomIncomingAttachmentWithoutSenderInfoBubbleCell.xib in Resources */, B1B558BB20EF768F00210D55 /* RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.xib in Resources */, B1B557A720EF5A1B00210D55 /* DeviceTableViewCell.xib in Resources */, + B14084C823BF76CB0010F692 /* BubbleCellContentView.xib in Resources */, B1B557D220EF5E3500210D55 /* MediaAlbumTableCell.xib in Resources */, B1B558C520EF768F00210D55 /* RoomOutgoingEncryptedTextMsgBubbleCell.xib in Resources */, B1B5582B20EF666100210D55 /* DirectoryRecentTableViewCell.xib in Resources */, @@ -4198,6 +4271,7 @@ B1B557A220EF58AD00210D55 /* ContactTableViewCell.xib in Resources */, B1B558EB20EF768F00210D55 /* RoomIncomingTextMsgWithPaginationTitleBubbleCell.xib in Resources */, B10B3B5C2201DD740072C76B /* KeyBackupBannerCell.xib in Resources */, + B1C543A6239E999700DCA1FA /* KeyVerificationCellInnerContentView.xib in Resources */, 32891D76226728EF00C82226 /* DeviceVerificationDataLoadingViewController.storyboard in Resources */, B1B5581820EF625800210D55 /* PreviewRoomTitleView.xib in Resources */, B1B5583020EF66BA00210D55 /* RoomIdOrAliasTableViewCell.xib in Resources */, @@ -4481,6 +4555,7 @@ B169330B20F3CA3A00746532 /* Contact.m in Sources */, B1A5B33E227ADF2A004CBA85 /* UIImage.swift in Sources */, B1D4752A21EE52B10067973F /* KeyBackupSetupIntroViewController.swift in Sources */, + B108931F23AB80EF00802670 /* KeyVerificationIncomingRequestApprovalBubbleCell.swift in Sources */, B1B5599220EFC5E400210D55 /* Analytics.m in Sources */, B14F143422144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewAction.swift in Sources */, B1098BF621ECFE65000DDA48 /* KeyBackupSetupPassphraseCoordinator.swift in Sources */, @@ -4521,6 +4596,7 @@ B1B5581A20EF625800210D55 /* ExpandedRoomTitleView.m in Sources */, B1107EC82200B0720038014B /* KeyBackupRecoverSuccessViewController.swift in Sources */, B1B9DEEB22EB34EF0065E677 /* ReactionHistoryViewModel.swift in Sources */, + B1C543B023A2871300DCA1FA /* KeyVerificationBaseBubbleCell.swift in Sources */, B1963B2F228F1C4900CBA17F /* BubbleReactionViewCell.swift in Sources */, B1B558E920EF768F00210D55 /* RoomSelectedStickerBubbleCell.m in Sources */, B1C3360222F1ED600021BA8D /* MediaPickerCoordinatorBridgePresenter.swift in Sources */, @@ -4535,6 +4611,7 @@ 3209451221F1C1430088CAA2 /* BlackTheme.swift in Sources */, B1B5572720EE6C4D00210D55 /* RoomSearchViewController.m in Sources */, 3232ABBC2257BE6500AD6A5C /* DeviceVerificationVerifyViewAction.swift in Sources */, + B14084C623BF76890010F692 /* BubbleCellContentView.swift in Sources */, F05927C91FDED836009F2A68 /* MXGroup+Riot.m in Sources */, B1B5594520EF7BD000210D55 /* TableViewCellWithCollectionView.m in Sources */, B1A6C109238828A6002882FD /* SlidingModalPresentationDelegate.swift in Sources */, @@ -4570,11 +4647,13 @@ 32242F1421E8FBA900725742 /* DefaultTheme.swift in Sources */, B1963B2D228F1C4900CBA17F /* BubbleReactionsViewModel.swift in Sources */, 32242F1321E8FBA900725742 /* Theme.swift in Sources */, + B108932523AB93A200802670 /* KeyVerificationConclusionViewData.swift in Sources */, B1B5582520EF638A00210D55 /* RoomMemberTitleView.m in Sources */, B1B5582C20EF666100210D55 /* DirectoryRecentTableViewCell.m in Sources */, B1B558E420EF768F00210D55 /* RoomMembershipWithPaginationTitleBubbleCell.m in Sources */, B1B5573620EE6C4D00210D55 /* GroupsViewController.m in Sources */, B125FE21231D5E1D00B72806 /* SettingsDiscoveryViewAction.swift in Sources */, + B108932323AB908A00802670 /* KeyVerificationRequestStatusViewData.swift in Sources */, 3232ABB82257BE6500AD6A5C /* DeviceVerificationVerifyCoordinator.swift in Sources */, B142317A22CCFA2000FFA96A /* EditHistoryCell.swift in Sources */, B1DCC62622E60CC600625807 /* EmojiItem.swift in Sources */, @@ -4619,6 +4698,7 @@ 32863A5C2384074C00D07C4A /* RiotSettingAllowedWidgets.swift in Sources */, B1B9DEDA22E9B7350065E677 /* SerializationService.swift in Sources */, B1B5572520EE6C4D00210D55 /* RoomMessagesSearchViewController.m in Sources */, + B1C543AE23A286A000DCA1FA /* KeyVerificationRequestStatusBubbleCell.swift in Sources */, B139C22121FE5D9D00BB68EC /* KeyBackupRecoverFromPassphraseViewState.swift in Sources */, B1B5579120EF568D00210D55 /* GroupInviteTableViewCell.m in Sources */, B1B5579A20EF575B00210D55 /* ForgotPasswordInputsView.m in Sources */, @@ -4634,6 +4714,7 @@ B1B5574020EE6C4D00210D55 /* SegmentedViewController.m in Sources */, B1B5599320EFC5E400210D55 /* DecryptionFailure.m in Sources */, B125FE1F231D5DF700B72806 /* SettingsDiscoveryViewModelType.swift in Sources */, + B108932A23ACBA0B00802670 /* SizingViewHeight.swift in Sources */, B157FAA323264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewState.swift in Sources */, B1098BF921ECFE65000DDA48 /* KeyBackupSetupCoordinator.swift in Sources */, B140B4A821F8AB4600E3F5FE /* KeyBackupRecoverCoordinatorBridgePresenter.swift in Sources */, @@ -4652,10 +4733,12 @@ B1B557B420EF5AEF00210D55 /* EventDetailsView.m in Sources */, B1B5577E20EE84BF00210D55 /* IncomingCallView.m in Sources */, B1DCC62822E60CE300625807 /* EmojiCategory.swift in Sources */, + B14084CC23BF9DE90010F692 /* KeyVerificationConclusionWithPaginationTitleBubbleCell.swift in Sources */, B1B5578F20EF568D00210D55 /* GroupTableViewCell.m in Sources */, B1B5573220EE6C4D00210D55 /* GroupHomeViewController.m in Sources */, B1B5595220EF9A8700210D55 /* RecentTableViewCell.m in Sources */, 32F6B96C2270623100BBA352 /* DeviceVerificationDataLoadingCoordinatorType.swift in Sources */, + B14084CA23BF89310010F692 /* KeyVerificationRequestStatusWithPaginationTitleBubbleCell.swift in Sources */, B1DCC61D22E5E17100625807 /* EmojiPickerViewModelType.swift in Sources */, B1B5574120EE6C4D00210D55 /* RecentsViewController.m in Sources */, B1D250D82118AA0A000F4E93 /* RoomPredecessorBubbleCell.m in Sources */, @@ -4709,6 +4792,7 @@ B1B5575A20EE6C4D00210D55 /* UnifiedSearchViewController.m in Sources */, 3232AB492256558300AD6A5C /* FlowTemplateCoordinatorBridgePresenter.swift in Sources */, B1B5572820EE6C4D00210D55 /* RoomViewController.m in Sources */, + B108932123AB8D7D00802670 /* KeyVerificationIncomingRequestApprovalViewData.swift in Sources */, B1B9DEED22EB34EF0065E677 /* ReactionHistoryCoordinator.swift in Sources */, B1DCC62A22E60D1000625807 /* EmojiMartService.swift in Sources */, B1B558C720EF768F00210D55 /* RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.m in Sources */, @@ -4734,6 +4818,7 @@ B157FAA223264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewAction.swift in Sources */, 3232ABA1225730E100AD6A5C /* DeviceVerificationCoordinatorType.swift in Sources */, B1C562D9228C0B760037F12A /* RoomContextualMenuItem.swift in Sources */, + B1C543B223A2913F00DCA1FA /* KeyVerificationConclusionBubbleCell.swift in Sources */, 323AB947232BD74600C1451F /* AuthFallBackViewController.m in Sources */, B1C562E1228C7C8C0037F12A /* RoomContextualMenuToolbarView.swift in Sources */, B1B557BF20EF5B4500210D55 /* DisabledRoomInputToolbarView.m in Sources */, @@ -4745,6 +4830,7 @@ B1098C0D21ED07E4000DDA48 /* NavigationRouter.swift in Sources */, B110872321F098F0003554A5 /* ActivityIndicatorPresenterType.swift in Sources */, B139C22321FF01B200BB68EC /* KeyBackupRecoverFromPassphraseCoordinatorType.swift in Sources */, + B14084CE23BFA0990010F692 /* KeyVerificationIncomingRequestApprovalWithPaginationTitleBubbleCell.swift in Sources */, B14F143222144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyCoordinator.swift in Sources */, B110872621F098F0003554A5 /* ActivityIndicatorView.swift in Sources */, B19EFA3921F8BB2C00FC070E /* KeyBackupRecoverCoordinatorType.swift in Sources */, @@ -4802,11 +4888,13 @@ B1B5577D20EE84BF00210D55 /* CircleButton.m in Sources */, 32BF995521FA2AB700698084 /* SettingsKeyBackupViewAction.swift in Sources */, B109D6F1222D8C400061B6D9 /* UIApplication.swift in Sources */, + B108932823ABEE6800802670 /* BubbleCellReadReceiptsDisplayable.swift in Sources */, B1B558FF20EF768F00210D55 /* RoomIncomingTextMsgBubbleCell.m in Sources */, B1098C0021ECFE65000DDA48 /* KeyBackupSetupPassphraseViewController.swift in Sources */, B1B5591020EF782800210D55 /* TableViewCellWithPhoneNumberTextField.m in Sources */, B1DB4F06223015080065DBFA /* Character.swift in Sources */, B1B9DEEE22EB34EF0065E677 /* ReactionHistoryViewAction.swift in Sources */, + B1C543A4239E98E400DCA1FA /* KeyVerificationCellInnerContentView.swift in Sources */, 32B94DFA228EC26400716A26 /* ReactionsMenuButton.swift in Sources */, B1B9DEEC22EB34EF0065E677 /* ReactionHistoryViewModelType.swift in Sources */, B157FAA523264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModel.swift in Sources */, From c659e87a3236616336fa8b8d808fa6d354074aae Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 15 Jan 2020 08:38:35 +0100 Subject: [PATCH 067/282] Cross-signing: Follow API change on [MXCrypto downloadKeys:] --- Riot/AppDelegate.m | 2 +- .../Loading/DeviceVerificationDataLoadingViewModel.swift | 2 +- .../Room/Members/Detail/RoomMemberDetailsViewController.m | 2 +- Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.m | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 517395fb02..e986f3f00b 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -4701,7 +4701,7 @@ - (void)checkPendingRoomKeyRequestsInSession:(MXSession*)mxSession NSString *deviceId = [pendingKeyRequests deviceIdsForUser:userId].firstObject; // Give the client a chance to refresh the device list - [mxSession.crypto downloadKeys:@[userId] forceDownload:NO success:^(MXUsersDevicesMap *usersDevicesInfoMap) { + [mxSession.crypto downloadKeys:@[userId] forceDownload:NO success:^(MXUsersDevicesMap *usersDevicesInfoMap, NSDictionary *crossSigningKeysMap) { MXDeviceInfo *deviceInfo = [usersDevicesInfoMap objectForDevice:deviceId forUser:userId]; if (deviceInfo) diff --git a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewModel.swift b/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewModel.swift index ed9ddb2fa3..b1dae3b0af 100644 --- a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewModel.swift +++ b/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewModel.swift @@ -72,7 +72,7 @@ final class DeviceVerificationDataLoadingViewModel: DeviceVerificationDataLoadin self.update(viewState: .loading) - crypto.downloadKeys([self.otherUserId], forceDownload: false, success: { [weak self] (usersDevicesMap) in + crypto.downloadKeys([self.otherUserId], forceDownload: false, success: { [weak self] (usersDevicesMap, crossSigningKeysMap) in guard let sself = self else { return } diff --git a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m index d84d669ecc..89662ec224 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m @@ -406,7 +406,7 @@ - (void)updateMemberInfo NSString *userId = self.mxRoomMember.userId; __weak typeof(self) weakSelf = self; - [self.mxRoom.mxSession.crypto downloadKeys:@[userId] forceDownload:NO success:^(MXUsersDevicesMap *usersDevicesInfoMap) { + [self.mxRoom.mxSession.crypto downloadKeys:@[userId] forceDownload:NO success:^(MXUsersDevicesMap *usersDevicesInfoMap, NSDictionary *crossSigningKeysMap) { if (weakSelf) { diff --git a/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.m b/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.m index b73d7c2fed..ee8b6e916a 100644 --- a/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.m +++ b/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.m @@ -151,7 +151,7 @@ - (void)deviceVerificationCoordinatorBridgePresenterDelegateDidComplete:(DeviceV deviceVerificationCoordinatorBridgePresenter = nil; // Check device new status - [self.mxSession.crypto downloadKeys:@[self.device.userId] forceDownload:NO success:^(MXUsersDevicesMap *usersDevicesInfoMap) { + [self.mxSession.crypto downloadKeys:@[self.device.userId] forceDownload:NO success:^(MXUsersDevicesMap *usersDevicesInfoMap, NSDictionary *crossSigningKeysMap) { MXDeviceInfo *deviceInfo = [usersDevicesInfoMap objectForDevice:self.device.deviceId forUser:self.device.userId]; if (deviceInfo && deviceInfo.verified == MXDeviceVerified) From b41343f3f6f3f8aab755221423df419ac29e2fb8 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 15 Jan 2020 10:50:36 +0100 Subject: [PATCH 068/282] KeyVerificationConclusionBubbleCell: Update warning title and badge image. --- Riot/Assets/en.lproj/Vector.strings | 1 + Riot/Generated/Strings.swift | 4 ++++ .../KeyVerificationConclusionBubbleCell.swift | 20 ++----------------- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 2b9f1c04d5..b647ea9fb2 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -1106,3 +1106,4 @@ "key_verification_tile_request_incoming_approval_decline" = "Decline"; "key_verification_tile_conclusion_done_title" = "Verified"; +"key_verification_tile_conclusion_warning_title" = "Unstrusted sign in"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 6240b5928b..a45f06cdab 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -1474,6 +1474,10 @@ internal enum VectorL10n { internal static var keyVerificationTileConclusionDoneTitle: String { return VectorL10n.tr("Vector", "key_verification_tile_conclusion_done_title") } + /// Unstrusted sign in + internal static var keyVerificationTileConclusionWarningTitle: String { + return VectorL10n.tr("Vector", "key_verification_tile_conclusion_warning_title") + } /// Accept internal static var keyVerificationTileRequestIncomingApprovalAccept: String { return VectorL10n.tr("Vector", "key_verification_tile_request_incoming_approval_accept") diff --git a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionBubbleCell.swift index 41761b3808..70353531f8 100644 --- a/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionBubbleCell.swift +++ b/Riot/Modules/Room/Views/BubbleCells/KeyVerification/KeyVerificationConclusionBubbleCell.swift @@ -81,24 +81,8 @@ class KeyVerificationConclusionBubbleCell: KeyVerificationBaseBubbleCell { badgeImage = Asset.Images.encryptionTrusted.image title = VectorL10n.keyVerificationTileConclusionDoneTitle case .keyVerificationCancel: - badgeImage = Asset.Images.encryptionNormal.image - - // TODO: Use right titles here - if let keyVerification = roomBubbleData.keyVerification, let cancelCodeValue = keyVerification.transaction?.reasonCancelCode?.value { - switch cancelCodeValue { - case MXTransactionCancelCode.mismatchedSas().value: - title = "TODO" - case MXTransactionCancelCode.unexpectedMessage().value: - title = "TODO" - case MXTransactionCancelCode.mismatchedCommitment().value: - title = "TODO" - default: - title = nil - } - } else { - title = nil - } - + badgeImage = Asset.Images.encryptionWarning.image + title = VectorL10n.keyVerificationTileConclusionWarningTitle default: badgeImage = nil title = nil From dd3740aad658118831f1c1466e8c82558b986d57 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 15 Jan 2020 18:18:22 +0100 Subject: [PATCH 069/282] Update Riot/Assets/en.lproj/Vector.strings Co-Authored-By: manuroe --- Riot/Assets/en.lproj/Vector.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index b647ea9fb2..e9618727f6 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -1096,7 +1096,7 @@ "key_verification_tile_request_outgoing_title" = "Verification sent"; "key_verification_tile_request_status_data_loading" = "Data loading …"; -"key_verification_tile_request_status_waiting" = "Waiting …"; +"key_verification_tile_request_status_waiting" = "Waiting…"; "key_verification_tile_request_status_expired" = "Expired"; "key_verification_tile_request_status_cancelled_by_me" = "You cancel"; "key_verification_tile_request_status_cancelled" = "%@ cancelled"; From cd7b31fb8fb40a0bb0624123ea8512a7cc02f131 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 15 Jan 2020 18:18:32 +0100 Subject: [PATCH 070/282] Update Riot/Assets/en.lproj/Vector.strings Co-Authored-By: manuroe --- Riot/Assets/en.lproj/Vector.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index e9618727f6..668072889b 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -1095,7 +1095,7 @@ "key_verification_tile_request_incoming_title" = "Verification request"; "key_verification_tile_request_outgoing_title" = "Verification sent"; -"key_verification_tile_request_status_data_loading" = "Data loading …"; +"key_verification_tile_request_status_data_loading" = "Data loading…"; "key_verification_tile_request_status_waiting" = "Waiting…"; "key_verification_tile_request_status_expired" = "Expired"; "key_verification_tile_request_status_cancelled_by_me" = "You cancel"; From 2025d09e9c228cae78af05a7871a04db9a2677c4 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 15 Jan 2020 18:18:46 +0100 Subject: [PATCH 071/282] Update Riot/Assets/en.lproj/Vector.strings Co-Authored-By: manuroe --- Riot/Assets/en.lproj/Vector.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 668072889b..2870a0e011 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -1098,7 +1098,7 @@ "key_verification_tile_request_status_data_loading" = "Data loading…"; "key_verification_tile_request_status_waiting" = "Waiting…"; "key_verification_tile_request_status_expired" = "Expired"; -"key_verification_tile_request_status_cancelled_by_me" = "You cancel"; +"key_verification_tile_request_status_cancelled_by_me" = "You cancelled"; "key_verification_tile_request_status_cancelled" = "%@ cancelled"; "key_verification_tile_request_status_accepted" = "You accepted"; From 3d7c4689366a88371cb01dbd679f45b3dcbbe020 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 16 Jan 2020 09:19:39 +0100 Subject: [PATCH 072/282] Cross-signing: Follow API change on MXDeviceInfo.trustLevel --- Riot/AppDelegate.m | 2 +- Riot/Modules/EncryptionInfo/EncryptionInfoView.m | 2 +- .../Encryption/RoomEncryptedDataBubbleCell.m | 2 +- .../RoomKeyRequest/RoomKeyRequestViewController.m | 2 +- .../SettingsKeyBackupTableViewSection.swift | 12 ++++++++---- .../Modules/UserDevices/UsersDevicesViewController.m | 6 +++--- Riot/Modules/UserDevices/Views/DeviceTableViewCell.m | 6 +++--- 7 files changed, 18 insertions(+), 14 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index e986f3f00b..86e93d0283 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -4706,7 +4706,7 @@ - (void)checkPendingRoomKeyRequestsInSession:(MXSession*)mxSession MXDeviceInfo *deviceInfo = [usersDevicesInfoMap objectForDevice:deviceId forUser:userId]; if (deviceInfo) { - BOOL wasNewDevice = (deviceInfo.verified == MXDeviceUnknown); + BOOL wasNewDevice = (deviceInfo.trustLevel.localVerificationStatus == MXDeviceUnknown); void (^openDialog)(void) = ^void() { diff --git a/Riot/Modules/EncryptionInfo/EncryptionInfoView.m b/Riot/Modules/EncryptionInfo/EncryptionInfoView.m index 4d858639cb..341a397b1e 100644 --- a/Riot/Modules/EncryptionInfo/EncryptionInfoView.m +++ b/Riot/Modules/EncryptionInfo/EncryptionInfoView.m @@ -54,7 +54,7 @@ - (void)displayLegacyVerificationScreen - (void)onButtonPressed:(id)sender { UIViewController *rootViewController = [AppDelegate theDelegate].window.rootViewController; - if (sender == self.verifyButton && self.mxDeviceInfo.verified != MXDeviceVerified + if (sender == self.verifyButton && self.mxDeviceInfo.trustLevel.localVerificationStatus != MXDeviceVerified && self.mxDeviceInfo && rootViewController) { diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m index ab4c455e5d..009654230d 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m +++ b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m @@ -51,7 +51,7 @@ + (UIImage*)encryptionIconForEvent:(MXEvent*)event andSession:(MXSession*)sessio if (deviceInfo) { - switch (deviceInfo.verified) + switch (deviceInfo.trustLevel.localVerificationStatus) { case MXDeviceUnknown: case MXDeviceUnverified: diff --git a/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.m b/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.m index ee8b6e916a..162a0f6821 100644 --- a/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.m +++ b/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.m @@ -154,7 +154,7 @@ - (void)deviceVerificationCoordinatorBridgePresenterDelegateDidComplete:(DeviceV [self.mxSession.crypto downloadKeys:@[self.device.userId] forceDownload:NO success:^(MXUsersDevicesMap *usersDevicesInfoMap, NSDictionary *crossSigningKeysMap) { MXDeviceInfo *deviceInfo = [usersDevicesInfoMap objectForDevice:self.device.deviceId forUser:self.device.userId]; - if (deviceInfo && deviceInfo.verified == MXDeviceVerified) + if (deviceInfo && deviceInfo.trustLevel.localVerificationStatus == MXDeviceVerified) { // Accept the received requests from this device // As the device is now verified, all other key requests will be automatically accepted. diff --git a/Riot/Modules/Settings/KeyBackup/SettingsKeyBackupTableViewSection.swift b/Riot/Modules/Settings/KeyBackup/SettingsKeyBackupTableViewSection.swift index b13176cb16..d63ce69bec 100644 --- a/Riot/Modules/Settings/KeyBackup/SettingsKeyBackupTableViewSection.swift +++ b/Riot/Modules/Settings/KeyBackup/SettingsKeyBackupTableViewSection.swift @@ -248,13 +248,17 @@ private enum BackupRows { if device.fingerprint == self.userDevice.fingerprint { return VectorL10n.settingsKeyBackupInfoTrustSignatureValid - } else if signature.valid && (device.verified == MXDeviceVerified) { + } else if signature.valid + && (device.trustLevel.localVerificationStatus == .verified) { return VectorL10n.settingsKeyBackupInfoTrustSignatureValidDeviceVerified(displayName) - } else if signature.valid && (device.verified != MXDeviceVerified) { + } else if signature.valid + && (device.trustLevel.localVerificationStatus != .verified) { return VectorL10n.settingsKeyBackupInfoTrustSignatureValidDeviceUnverified(displayName) - } else if !signature.valid && (device.verified == MXDeviceVerified) { + } else if !signature.valid + && (device.trustLevel.localVerificationStatus == .verified) { return VectorL10n.settingsKeyBackupInfoTrustSignatureInvalidDeviceVerified(displayName) - } else if !signature.valid && (device.verified != MXDeviceVerified) { + } else if !signature.valid + && (device.trustLevel.localVerificationStatus != .verified) { return VectorL10n.settingsKeyBackupInfoTrustSignatureInvalidDeviceUnverified(displayName) } diff --git a/Riot/Modules/UserDevices/UsersDevicesViewController.m b/Riot/Modules/UserDevices/UsersDevicesViewController.m index 224b620b3c..89bce3363f 100644 --- a/Riot/Modules/UserDevices/UsersDevicesViewController.m +++ b/Riot/Modules/UserDevices/UsersDevicesViewController.m @@ -228,7 +228,7 @@ - (void)deviceTableViewCell:(DeviceTableViewCell *)deviceTableViewCell updateDev ofUser:deviceTableViewCell.deviceInfo.userId success:^{ - deviceTableViewCell.deviceInfo.verified = verificationStatus; + //deviceTableViewCell.deviceInfo.verified = verificationStatus; [self.tableView reloadData]; } failure:nil]; @@ -244,13 +244,13 @@ - (void)deviceVerificationCoordinatorBridgePresenterDelegateDidComplete:(DeviceV // Update our map MXWeakify(self); - [mxSession.crypto downloadKeys:@[otherUserId] forceDownload:NO success:^(MXUsersDevicesMap *usersDevicesInfoMap) { + [mxSession.crypto downloadKeys:@[otherUserId] forceDownload:NO success:^(MXUsersDevicesMap *usersDevicesInfoMap, NSDictionary *crossSigningKeysMap) { MXStrongifyAndReturnIfNil(self); MXDeviceInfo *deviceInfo = [usersDevicesInfoMap objectForDevice:otherDeviceId forUser:otherUserId]; MXDeviceInfo *device = [self->usersDevices objectForDevice:otherDeviceId forUser:otherUserId]; - device.verified = deviceInfo.verified; + //device.verified = deviceInfo.verified; [self.tableView reloadData]; diff --git a/Riot/Modules/UserDevices/Views/DeviceTableViewCell.m b/Riot/Modules/UserDevices/Views/DeviceTableViewCell.m index 9ee73a7789..41bf6485f6 100644 --- a/Riot/Modules/UserDevices/Views/DeviceTableViewCell.m +++ b/Riot/Modules/UserDevices/Views/DeviceTableViewCell.m @@ -50,7 +50,7 @@ - (void)render:(MXDeviceInfo *)deviceInfo self.deviceName.numberOfLines = 0; self.deviceName.text = (deviceInfo.displayName.length ? [NSString stringWithFormat:@"%@ (%@)", deviceInfo.displayName, deviceInfo.deviceId] : [NSString stringWithFormat:@"(%@)", deviceInfo.deviceId]); - switch (deviceInfo.verified) + switch (deviceInfo.trustLevel.localVerificationStatus) { case MXDeviceUnknown: case MXDeviceUnverified: @@ -110,11 +110,11 @@ - (IBAction)onButtonPressed:(id)sender if (sender == _verifyButton) { - verificationStatus = ((_deviceInfo.verified == MXDeviceVerified) ? MXDeviceUnverified : MXDeviceVerified); + verificationStatus = ((_deviceInfo.trustLevel.localVerificationStatus == MXDeviceVerified) ? MXDeviceUnverified : MXDeviceVerified); } else if (sender == _blockButton) { - verificationStatus = ((_deviceInfo.verified == MXDeviceBlocked) ? MXDeviceUnverified : MXDeviceBlocked); + verificationStatus = ((_deviceInfo.trustLevel.localVerificationStatus == MXDeviceBlocked) ? MXDeviceUnverified : MXDeviceBlocked); } else { From c8a1d8fdef2499123ca8790f838a9d4ecfcf01aa Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 16 Jan 2020 11:02:46 +0100 Subject: [PATCH 073/282] Cross-signing: Fix a missed API break --- .../UserDevices/UsersDevicesViewController.m | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Riot/Modules/UserDevices/UsersDevicesViewController.m b/Riot/Modules/UserDevices/UsersDevicesViewController.m index 89bce3363f..4bd8b21f93 100644 --- a/Riot/Modules/UserDevices/UsersDevicesViewController.m +++ b/Riot/Modules/UserDevices/UsersDevicesViewController.m @@ -227,14 +227,21 @@ - (void)deviceTableViewCell:(DeviceTableViewCell *)deviceTableViewCell updateDev forDevice:deviceTableViewCell.deviceInfo.deviceId ofUser:deviceTableViewCell.deviceInfo.userId success:^{ - - //deviceTableViewCell.deviceInfo.verified = verificationStatus; - [self.tableView reloadData]; - + [self reloadDataforUser:deviceTableViewCell.deviceInfo.userId andDevice:deviceTableViewCell.deviceInfo.deviceId]; } failure:nil]; } } +- (void)reloadDataforUser:(NSString *)userId andDevice:(NSString *)deviceId +{ + // Refresh data + MXDeviceInfo *device = [mxSession.crypto deviceInfoForDevice:deviceId ofUser:userId]; + [usersDevices setObject:device forUser:userId andDevice:deviceId]; + + // and reload + [self.tableView reloadData]; +} + #pragma mark - DeviceVerificationCoordinatorBridgePresenterDelegate - (void)deviceVerificationCoordinatorBridgePresenterDelegateDidComplete:(DeviceVerificationCoordinatorBridgePresenter *)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId @@ -243,16 +250,9 @@ - (void)deviceVerificationCoordinatorBridgePresenterDelegateDidComplete:(DeviceV deviceVerificationCoordinatorBridgePresenter = nil; // Update our map - MXWeakify(self); [mxSession.crypto downloadKeys:@[otherUserId] forceDownload:NO success:^(MXUsersDevicesMap *usersDevicesInfoMap, NSDictionary *crossSigningKeysMap) { - MXStrongifyAndReturnIfNil(self); - - MXDeviceInfo *deviceInfo = [usersDevicesInfoMap objectForDevice:otherDeviceId forUser:otherUserId]; - - MXDeviceInfo *device = [self->usersDevices objectForDevice:otherDeviceId forUser:otherUserId]; - //device.verified = deviceInfo.verified; - - [self.tableView reloadData]; + + [self reloadDataforUser:otherUserId andDevice:otherDeviceId]; } failure:^(NSError *error) { // Should not happen (the device is in the crypto db) From 9cf4d1a77e515519b649174658817756528b24ce Mon Sep 17 00:00:00 2001 From: Osoitz Date: Wed, 15 Jan 2020 18:35:30 +0000 Subject: [PATCH 074/282] Translated using Weblate (Basque) Currently translated at 100.0% (862 of 862 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/eu/ --- Riot/Assets/eu.lproj/Vector.strings | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Riot/Assets/eu.lproj/Vector.strings b/Riot/Assets/eu.lproj/Vector.strings index cd2c527e9a..ca88973a34 100644 --- a/Riot/Assets/eu.lproj/Vector.strings +++ b/Riot/Assets/eu.lproj/Vector.strings @@ -929,3 +929,15 @@ "settings_labs_dm_key_verification" = "Gako egiaztaketa mezu zuzenaren bidez"; "settings_labs_cross_signing" = "Zeharkako sinadura"; "service_terms_modal_policy_checkbox_accessibility_hint" = "Markatu %@ onartzeko"; +"key_verification_tile_request_incoming_title" = "Egiaztaketa eskaria"; +"key_verification_tile_request_outgoing_title" = "Egiaztaketa bidalita"; +"key_verification_tile_request_status_data_loading" = "Datuak kargatzen…"; +"key_verification_tile_request_status_waiting" = "Itxaroten…"; +"key_verification_tile_request_status_expired" = "Iraungita"; +"key_verification_tile_request_status_cancelled_by_me" = "Utzi duzu"; +"key_verification_tile_request_status_cancelled" = "%@(e)k utzita"; +"key_verification_tile_request_status_accepted" = "Onartu duzu"; +"key_verification_tile_request_incoming_approval_accept" = "Onartu"; +"key_verification_tile_request_incoming_approval_decline" = "Ukatu"; +"key_verification_tile_conclusion_done_title" = "Egiaztatuta"; +"key_verification_tile_conclusion_warning_title" = "Fidagarritasun gabeko saio hasiera"; From 703fc0b5c48cdb3cd6ca5ba1a40b1d83b4f52157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Thu, 16 Jan 2020 08:21:05 +0000 Subject: [PATCH 075/282] Translated using Weblate (French) Currently translated at 100.0% (862 of 862 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/fr/ --- Riot/Assets/fr.lproj/Vector.strings | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index 84603356f5..57943357de 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -944,3 +944,15 @@ "service_terms_modal_policy_checkbox_accessibility_hint" = "Cochez pour accepter %@"; "settings_labs_dm_key_verification" = "Vérification de clé par message direct"; "settings_labs_cross_signing" = "Signature croisée"; +"key_verification_tile_request_incoming_title" = "Demande de vérification"; +"key_verification_tile_request_outgoing_title" = "Vérification envoyée"; +"key_verification_tile_request_status_data_loading" = "Chargement des données…"; +"key_verification_tile_request_status_waiting" = "En attente…"; +"key_verification_tile_request_status_expired" = "Expiré"; +"key_verification_tile_request_status_cancelled_by_me" = "Vous avez annulé"; +"key_verification_tile_request_status_cancelled" = "%@ a annulé"; +"key_verification_tile_request_status_accepted" = "Vous avez accepté"; +"key_verification_tile_request_incoming_approval_accept" = "Accepter"; +"key_verification_tile_request_incoming_approval_decline" = "Refuser"; +"key_verification_tile_conclusion_done_title" = "Vérifié"; +"key_verification_tile_conclusion_warning_title" = "Connexion non approuvée"; From bedccde326efbeff4c5fa3ee772e01c1e1d5c4bc Mon Sep 17 00:00:00 2001 From: Szimszon Date: Wed, 15 Jan 2020 21:16:40 +0000 Subject: [PATCH 076/282] Translated using Weblate (Hungarian) Currently translated at 100.0% (862 of 862 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/hu/ --- Riot/Assets/hu.lproj/Vector.strings | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index ebcca5dcce..d81a7508bb 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -946,3 +946,15 @@ "service_terms_modal_policy_checkbox_accessibility_hint" = "Az engedélyezéshez jelöld be: %@"; "settings_labs_dm_key_verification" = "Kulcs ellenőrzés közvetlen üzenetben"; "settings_labs_cross_signing" = "Kereszt-aláírás"; +"key_verification_tile_request_incoming_title" = "Ellenőrzési kérés"; +"key_verification_tile_request_outgoing_title" = "Ellenőrzés kérése elküldve"; +"key_verification_tile_request_status_data_loading" = "Adat betöltés…"; +"key_verification_tile_request_status_waiting" = "Várakozik…"; +"key_verification_tile_request_status_expired" = "Lejárt"; +"key_verification_tile_request_status_cancelled_by_me" = "Megszakítottad"; +"key_verification_tile_request_status_cancelled" = "%@ megszakította"; +"key_verification_tile_request_status_accepted" = "Elfogadtad"; +"key_verification_tile_request_incoming_approval_accept" = "Elfogad"; +"key_verification_tile_request_incoming_approval_decline" = "Elutasít"; +"key_verification_tile_conclusion_done_title" = "Hitelesített"; +"key_verification_tile_conclusion_warning_title" = "Megbízhatatlan belépés"; From 1d57c82f2f309c16c17cc6cdc6c765f1e106f250 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 16 Jan 2020 12:58:14 +0100 Subject: [PATCH 077/282] Fix build --- Riot/Generated/Strings.swift | 6 +++--- Riot/Modules/UserDevices/UsersDevicesViewController.m | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index a45f06cdab..0babab6a62 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -1502,11 +1502,11 @@ internal enum VectorL10n { internal static func keyVerificationTileRequestStatusCancelled(_ p1: String) -> String { return VectorL10n.tr("Vector", "key_verification_tile_request_status_cancelled", p1) } - /// You cancel + /// You cancelled internal static var keyVerificationTileRequestStatusCancelledByMe: String { return VectorL10n.tr("Vector", "key_verification_tile_request_status_cancelled_by_me") } - /// Data loading … + /// Data loading… internal static var keyVerificationTileRequestStatusDataLoading: String { return VectorL10n.tr("Vector", "key_verification_tile_request_status_data_loading") } @@ -1514,7 +1514,7 @@ internal enum VectorL10n { internal static var keyVerificationTileRequestStatusExpired: String { return VectorL10n.tr("Vector", "key_verification_tile_request_status_expired") } - /// Waiting … + /// Waiting… internal static var keyVerificationTileRequestStatusWaiting: String { return VectorL10n.tr("Vector", "key_verification_tile_request_status_waiting") } diff --git a/Riot/Modules/UserDevices/UsersDevicesViewController.m b/Riot/Modules/UserDevices/UsersDevicesViewController.m index 4bd8b21f93..82501d259d 100644 --- a/Riot/Modules/UserDevices/UsersDevicesViewController.m +++ b/Riot/Modules/UserDevices/UsersDevicesViewController.m @@ -235,7 +235,7 @@ - (void)deviceTableViewCell:(DeviceTableViewCell *)deviceTableViewCell updateDev - (void)reloadDataforUser:(NSString *)userId andDevice:(NSString *)deviceId { // Refresh data - MXDeviceInfo *device = [mxSession.crypto deviceInfoForDevice:deviceId ofUser:userId]; + MXDeviceInfo *device = [mxSession.crypto deviceWithDeviceId:deviceId ofUser:userId]; [usersDevices setObject:device forUser:userId andDevice:deviceId]; // and reload From 9f562b8bf78bd177e563d54d4c1ae25bf07ee982 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 17 Jan 2020 10:52:26 +0100 Subject: [PATCH 078/282] Key verification: Present an alert when receiving incoming key verification request in foreground. --- Riot/AppDelegate.m | 79 +++++++++++++++++++++++++++++ Riot/Assets/en.lproj/Vector.strings | 4 ++ Riot/Generated/Strings.swift | 4 ++ 3 files changed, 87 insertions(+) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 2f04b8a937..984412ef4e 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -237,6 +237,8 @@ The current call view controller (if any). @property (weak, nonatomic) UIAlertController *gdprConsentNotGivenAlertController; @property (weak, nonatomic) UIViewController *gdprConsentController; +@property (weak, nonatomic) UIAlertController *incomingKeyVerificationRequestAlertController; + @property (nonatomic, strong) ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter; @property (nonatomic, strong) SlidingModalPresenter *slidingModalPresenter; @@ -694,6 +696,9 @@ - (void)applicationDidBecomeActive:(UIApplication *)application // Observe wrong backup version [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyBackupStateDidChangeNotification:) name:kMXKeyBackupDidStateChangeNotification object:nil]; + + // Observe key verification request + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyVerificationRequestDidChangeNotification:) name:MXDeviceVerificationManagerNewRequestNotification object:nil]; // Resume all existing matrix sessions NSArray *mxAccounts = [MXKAccountManager sharedManager].activeAccounts; @@ -713,6 +718,80 @@ - (void)applicationDidBecomeActive:(UIApplication *)application [self handleLaunchAnimation]; } +- (void)keyVerificationRequestDidChangeNotification:(NSNotification *)notification +{ + NSDictionary *userInfo = notification.userInfo; + + MXKeyVerificationRequest *keyVerificationRequest = userInfo[MXDeviceVerificationManagerNotificationRequestKey]; + + if ([keyVerificationRequest isKindOfClass:MXKeyVerificationByDMRequest.class]) + { + MXKeyVerificationByDMRequest *keyVerificationByDMRequest = (MXKeyVerificationByDMRequest*)keyVerificationRequest; + + if (!keyVerificationByDMRequest.isFromMyUser && keyVerificationByDMRequest.state == MXKeyVerificationRequestStatePending) + { + MXKAccount *currentAccount = [MXKAccountManager sharedManager].activeAccounts.firstObject; + MXRoom *room = [currentAccount.mxSession roomWithRoomId:keyVerificationByDMRequest.roomId]; + if (!room) + { + NSLog(@"[AppDelegate][KeyVerification] keyVerificationRequestDidChangeNotification: Unknown room"); + return; + } + + NSString *sender = keyVerificationByDMRequest.sender; + + [room state:^(MXRoomState *roomState) { + + NSString *senderName = [roomState.members memberName:sender]; + + if (self.incomingKeyVerificationRequestAlertController) + { + [self.incomingKeyVerificationRequestAlertController dismissViewControllerAnimated:NO completion:nil]; + } + + NSMutableString *senderInfo = [NSMutableString stringWithString:sender]; + + if (senderName) + { + [senderInfo appendFormat:@" (%@)", senderName]; + } + + NSString *alertMessage = [NSString stringWithFormat:NSLocalizedStringFromTable(@"key_verification_incoming_request_incoming_alert_message", @"Vector", nil), senderInfo]; + + self.incomingKeyVerificationRequestAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"key_verification_tile_request_incoming_title", @"Vector", nil) + message:alertMessage + preferredStyle:UIAlertControllerStyleAlert]; + + [self.incomingKeyVerificationRequestAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"key_verification_tile_request_incoming_approval_accept", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) + { + [self presentIncomingKeyVerificationRequest:keyVerificationByDMRequest inSession:self.mxSessions.firstObject]; + }]]; + + [self.incomingKeyVerificationRequestAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"key_verification_tile_request_incoming_approval_decline", @"Vector", nil) + style:UIAlertActionStyleDestructive + handler:^(UIAlertAction * action) + { + [keyVerificationByDMRequest cancelWithCancelCode:MXTransactionCancelCode.user success:^{ + + } failure:^(NSError * _Nonnull error) { + NSLog(@"[AppDelegate][KeyVerification] Fail to cancel incoming key verification request with error: %@", error); + }]; + }]]; + + [self.incomingKeyVerificationRequestAlertController addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"later", @"Vector", nil) + style:UIAlertActionStyleCancel + handler:^(UIAlertAction * action) + { + }]]; + + [self showNotificationAlert:self.incomingKeyVerificationRequestAlertController]; + }]; + } + } +} + - (void)applicationWillTerminate:(UIApplication *)application { NSLog(@"[AppDelegate] applicationWillTerminate"); diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 2870a0e011..a18e8f8cee 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -1107,3 +1107,7 @@ "key_verification_tile_conclusion_done_title" = "Verified"; "key_verification_tile_conclusion_warning_title" = "Unstrusted sign in"; + +// Incoming key verification request + +"key_verification_incoming_request_incoming_alert_message" = "%@ wants to verify"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 0babab6a62..d2028bb4ee 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -1470,6 +1470,10 @@ internal enum VectorL10n { internal static var keyBackupSetupTitle: String { return VectorL10n.tr("Vector", "key_backup_setup_title") } + /// %@ wants to verify + internal static func keyVerificationIncomingRequestIncomingAlertMessage(_ p1: String) -> String { + return VectorL10n.tr("Vector", "key_verification_incoming_request_incoming_alert_message", p1) + } /// Verified internal static var keyVerificationTileConclusionDoneTitle: String { return VectorL10n.tr("Vector", "key_verification_tile_conclusion_done_title") From f73bb2593959aa6c167f981b3f8ab91f7acebbe1 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 17 Jan 2020 10:54:18 +0100 Subject: [PATCH 079/282] Key verification: Present a notification when receiving incoming key verification request in background. --- Riot/AppDelegate.m | 206 +++++++---------------- Riot/Assets/en.lproj/Localizable.strings | 4 + 2 files changed, 68 insertions(+), 142 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 984412ef4e..832d877879 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1679,132 +1679,6 @@ - (NSDictionary*)notificationUserInfoForEvent:(MXEvent*)event andUserId:(NSStrin return notificationUserInfo; } -- (void)notificationBodyForEvent:(MXEvent *)event pushRule:(MXPushRule*)rule inAccount:(MXKAccount*)account onComplete:(void (^)(NSString * _Nullable notificationBody))onComplete; -{ - if (!event.content || !event.content.count) - { - NSLog(@"[AppDelegate][Push] notificationBodyForEvent: empty event content"); - onComplete (nil); - return; - } - - MXRoom *room = [account.mxSession roomWithRoomId:event.roomId]; - if (!room) - { - NSLog(@"[AppDelegate][Push] notificationBodyForEvent: Unknown room"); - onComplete (nil); - return; - } - - [room state:^(MXRoomState *roomState) { - - NSString *notificationBody; - NSString *eventSenderName = [roomState.members memberName:event.sender]; - - if (event.eventType == MXEventTypeRoomMessage || event.eventType == MXEventTypeRoomEncrypted) - { - if (room.isMentionsOnly) - { - // A local notification will be displayed only for highlighted notification. - BOOL isHighlighted = NO; - - // Check whether is there an highlight tweak on it - for (MXPushRuleAction *ruleAction in rule.actions) - { - if (ruleAction.actionType == MXPushRuleActionTypeSetTweak) - { - if ([ruleAction.parameters[@"set_tweak"] isEqualToString:@"highlight"]) - { - // Check the highlight tweak "value" - // If not present, highlight. Else check its value before highlighting - if (nil == ruleAction.parameters[@"value"] || YES == [ruleAction.parameters[@"value"] boolValue]) - { - isHighlighted = YES; - break; - } - } - } - } - - if (!isHighlighted) - { - // Ignore this notif. - NSLog(@"[AppDelegate][Push] notificationBodyForEvent: Ignore non highlighted notif in mentions only room"); - onComplete(nil); - return; - } - } - - NSString *msgType = event.content[@"msgtype"]; - NSString *content = event.content[@"body"]; - - if (event.isEncrypted && !RiotSettings.shared.showDecryptedContentInNotifications) - { - // Hide the content - msgType = nil; - } - - NSString *roomDisplayName = room.summary.displayname; - - // Display the room name only if it is different than the sender name - if (roomDisplayName.length && ![roomDisplayName isEqualToString:eventSenderName]) - { - if ([msgType isEqualToString:@"m.text"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_IN_ROOM_WITH_CONTENT", nil), eventSenderName,roomDisplayName, content]; - else if ([msgType isEqualToString:@"m.emote"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"ACTION_FROM_USER_IN_ROOM", nil), roomDisplayName, eventSenderName, content]; - else if ([msgType isEqualToString:@"m.image"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"IMAGE_FROM_USER_IN_ROOM", nil), eventSenderName, content, roomDisplayName]; - else - // Encrypted messages falls here - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_IN_ROOM", nil), eventSenderName, roomDisplayName]; - } - else - { - if ([msgType isEqualToString:@"m.text"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_WITH_CONTENT", nil), eventSenderName, content]; - else if ([msgType isEqualToString:@"m.emote"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"ACTION_FROM_USER", nil), eventSenderName, content]; - else if ([msgType isEqualToString:@"m.image"]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"IMAGE_FROM_USER", nil), eventSenderName, content]; - else - // Encrypted messages falls here - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER", nil), eventSenderName]; - } - } - else if (event.eventType == MXEventTypeCallInvite) - { - NSString *sdp = event.content[@"offer"][@"sdp"]; - BOOL isVideoCall = [sdp rangeOfString:@"m=video"].location != NSNotFound; - - if (!isVideoCall) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"VOICE_CALL_FROM_USER", nil), eventSenderName]; - else - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"VIDEO_CALL_FROM_USER", nil), eventSenderName]; - } - else if (event.eventType == MXEventTypeRoomMember) - { - NSString *roomDisplayName = room.summary.displayname; - - if (roomDisplayName.length && ![roomDisplayName isEqualToString:eventSenderName]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"USER_INVITE_TO_NAMED_ROOM", nil), eventSenderName, roomDisplayName]; - else - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"USER_INVITE_TO_CHAT", nil), eventSenderName]; - } - else if (event.eventType == MXEventTypeSticker) - { - NSString *roomDisplayName = room.summary.displayname; - - if (roomDisplayName.length && ![roomDisplayName isEqualToString:eventSenderName]) - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER_IN_ROOM", nil), eventSenderName, roomDisplayName]; - else - notificationBody = [NSString stringWithFormat:NSLocalizedString(@"MSG_FROM_USER", nil), eventSenderName]; - } - - onComplete(notificationBody); - }]; -} - // iOS 10+, does the same thing as notificationBodyForEvent:pushRule:inAccount:onComplete:, except with more features - (void)notificationContentForEvent:(MXEvent *)event pushRule:(MXPushRule *)rule inAccount:(MXKAccount *)account onComplete:(void (^)(UNNotificationContent * _Nullable notificationContent))onComplete; { @@ -1830,6 +1704,7 @@ - (void)notificationContentForEvent:(MXEvent *)event pushRule:(MXPushRule *)rule NSString *threadIdentifier = room.roomId; NSString *eventSenderName = [roomState.members memberName:event.sender]; + NSString *currentUserId = account.mxCredentials.userId; if (event.eventType == MXEventTypeRoomMessage || event.eventType == MXEventTypeRoomEncrypted) { @@ -1876,6 +1751,9 @@ - (void)notificationContentForEvent:(MXEvent *)event pushRule:(MXPushRule *)rule NSString *roomDisplayName = room.summary.displayname; + NSString *myUserId = account.mxSession.myUser.userId; + BOOL isIncomingEvent = ![event.sender isEqualToString:myUserId]; + // Display the room name only if it is different than the sender name if (roomDisplayName.length && ![roomDisplayName isEqualToString:eventSenderName]) { @@ -1893,6 +1771,30 @@ - (void)notificationContentForEvent:(MXEvent *)event pushRule:(MXPushRule *)rule { notificationBody = [NSString localizedUserNotificationStringForKey:@"IMAGE_FROM_USER" arguments:@[eventSenderName, messageContent]]; } + else if (room.isDirect && isIncomingEvent && [msgType isEqualToString:kMXMessageTypeKeyVerificationRequest]) + { + [account.mxSession.crypto.deviceVerificationManager keyVerificationFromKeyVerificationEvent:event + success:^(MXKeyVerification * _Nonnull keyVerification) + { + if (keyVerification && keyVerification.state == MXKeyVerificationRequestStatePending) + { + // TODO: Add accept and decline actions to notification + NSString *body = [NSString localizedUserNotificationStringForKey:@"KEY_VERIFICATION_REQUEST_FROM_USER" arguments:@[eventSenderName]]; + + UNNotificationContent *notificationContent = [self notificationContentWithTitle:notificationTitle + body:body + threadIdentifier:threadIdentifier + userId:currentUserId + event:event + pushRule:rule]; + + onComplete(notificationContent); + } + + } failure:^(NSError * _Nonnull error) { + NSLog(@"[AppDelegate][Push] notificationContentForEvent: failed to fetch key verification with error: %@", error); + }]; + } else { // Encrypted messages falls here @@ -1968,27 +1870,47 @@ - (void)notificationContentForEvent:(MXEvent *)event pushRule:(MXPushRule *)rule notificationBody = [NSString localizedUserNotificationStringForKey:@"STICKER_FROM_USER" arguments:@[eventSenderName]]; } - UNMutableNotificationContent *notificationContent = [[UNMutableNotificationContent alloc] init]; - - NSDictionary *notificationUserInfo = [self notificationUserInfoForEvent:event andUserId:account.mxCredentials.userId]; - NSString *notificationSoundName = [self notificationSoundNameFromPushRule:rule]; - NSString *categoryIdentifier = [self notificationCategoryIdentifierForEvent:event]; - - notificationContent.title = notificationTitle; - notificationContent.body = notificationBody; - notificationContent.threadIdentifier = threadIdentifier; - notificationContent.userInfo = notificationUserInfo; - notificationContent.categoryIdentifier = categoryIdentifier; - - if (notificationSoundName) + if (notificationBody) { - notificationContent.sound = [UNNotificationSound soundNamed:notificationSoundName]; + UNNotificationContent *notificationContent = [self notificationContentWithTitle:notificationTitle + body:notificationBody + threadIdentifier:threadIdentifier + userId:currentUserId + event:event + pushRule:rule]; + + onComplete(notificationContent); } - - onComplete([notificationContent copy]); }]; } +- (UNNotificationContent*)notificationContentWithTitle:(NSString*)title + body:(NSString*)body + threadIdentifier:(NSString*)threadIdentifier + userId:(NSString*)userId + event:(MXEvent*)event + pushRule:(MXPushRule*)pushRule +{ + UNMutableNotificationContent *notificationContent = [[UNMutableNotificationContent alloc] init]; + + NSDictionary *notificationUserInfo = [self notificationUserInfoForEvent:event andUserId:userId]; + NSString *notificationSoundName = [self notificationSoundNameFromPushRule:pushRule]; + NSString *categoryIdentifier = [self notificationCategoryIdentifierForEvent:event]; + + notificationContent.title = title; + notificationContent.body = body; + notificationContent.threadIdentifier = threadIdentifier; + notificationContent.userInfo = notificationUserInfo; + notificationContent.categoryIdentifier = categoryIdentifier; + + if (notificationSoundName) + { + notificationContent.sound = [UNNotificationSound soundNamed:notificationSoundName]; + } + + return [notificationContent copy]; +} + /** Display "limited" notifications for events the app was not able to get data (because of /sync failure). diff --git a/Riot/Assets/en.lproj/Localizable.strings b/Riot/Assets/en.lproj/Localizable.strings index fc5cc015a8..61d8a3e98c 100644 --- a/Riot/Assets/en.lproj/Localizable.strings +++ b/Riot/Assets/en.lproj/Localizable.strings @@ -109,3 +109,7 @@ /* Incoming named video conference invite from a specific person */ "VIDEO_CONF_NAMED_FROM_USER" = "Video group call from %@: '%@'"; + +/** Key verification **/ + +"KEY_VERIFICATION_REQUEST_FROM_USER" = "%@ wants to verify"; From a7d64864100445ea66dec5ffe1569a0dc1fcd6c7 Mon Sep 17 00:00:00 2001 From: random Date: Thu, 16 Jan 2020 13:03:42 +0000 Subject: [PATCH 080/282] Translated using Weblate (Italian) Currently translated at 100.0% (862 of 862 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/it/ --- Riot/Assets/it.lproj/Vector.strings | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Riot/Assets/it.lproj/Vector.strings b/Riot/Assets/it.lproj/Vector.strings index 90359ddb51..f0348542e4 100644 --- a/Riot/Assets/it.lproj/Vector.strings +++ b/Riot/Assets/it.lproj/Vector.strings @@ -916,3 +916,15 @@ "service_terms_modal_policy_checkbox_accessibility_hint" = "Seleziona per accettare %@"; "settings_labs_dm_key_verification" = "Verifica chiave con messaggio diretto"; "settings_labs_cross_signing" = "Firma incrociata"; +"key_verification_tile_request_incoming_title" = "Richiesta di verifica"; +"key_verification_tile_request_outgoing_title" = "Verifica inviata"; +"key_verification_tile_request_status_data_loading" = "Caricamento dati…"; +"key_verification_tile_request_status_waiting" = "In attesa…"; +"key_verification_tile_request_status_expired" = "Scaduta"; +"key_verification_tile_request_status_cancelled_by_me" = "Hai annullato"; +"key_verification_tile_request_status_cancelled" = "%@ ha annullato"; +"key_verification_tile_request_status_accepted" = "Hai accettato"; +"key_verification_tile_request_incoming_approval_accept" = "Accetta"; +"key_verification_tile_request_incoming_approval_decline" = "Rifiuta"; +"key_verification_tile_conclusion_done_title" = "Verificato"; +"key_verification_tile_conclusion_warning_title" = "Accesso non fidato"; From 1cca4fa2306cd966ee14202bf78e3939db6c9684 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 17 Jan 2020 15:52:49 +0100 Subject: [PATCH 081/282] Key verification: Present incoming key verification request alert only when the app is in foreground. --- Riot/AppDelegate.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 832d877879..85db64b6fb 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -720,6 +720,11 @@ - (void)applicationDidBecomeActive:(UIApplication *)application - (void)keyVerificationRequestDidChangeNotification:(NSNotification *)notification { + if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground) + { + return; + } + NSDictionary *userInfo = notification.userInfo; MXKeyVerificationRequest *keyVerificationRequest = userInfo[MXDeviceVerificationManagerNotificationRequestKey]; From 88daaa625f3d375fa44c4f08ee64214fed8a0cae Mon Sep 17 00:00:00 2001 From: Osoitz Date: Sat, 18 Jan 2020 11:42:39 +0000 Subject: [PATCH 082/282] Translated using Weblate (Basque) Currently translated at 100.0% (29 of 29 strings) Translation: Riot iOS/Riot iOS (Push) Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios-push/eu/ --- Riot/Assets/eu.lproj/Localizable.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Assets/eu.lproj/Localizable.strings b/Riot/Assets/eu.lproj/Localizable.strings index a17db7e88e..b39ed2681f 100644 --- a/Riot/Assets/eu.lproj/Localizable.strings +++ b/Riot/Assets/eu.lproj/Localizable.strings @@ -54,3 +54,4 @@ "MSG_FROM_USER_IN_ROOM_TITLE" = "%@ erabiltzailea %@ gelan"; /* Sticker from a specific person, not referencing a room. */ "STICKER_FROM_USER" = "%@ erabiltzaileak eranskailu bat bidali du"; +"KEY_VERIFICATION_REQUEST_FROM_USER" = "%@(e)k egiaztatu nahi du"; From 1aa974fef6739c28258f5be85d9bf26b01e0d141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Sat, 18 Jan 2020 07:39:17 +0000 Subject: [PATCH 083/282] Translated using Weblate (French) Currently translated at 100.0% (29 of 29 strings) Translation: Riot iOS/Riot iOS (Push) Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios-push/fr/ --- Riot/Assets/fr.lproj/Localizable.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Assets/fr.lproj/Localizable.strings b/Riot/Assets/fr.lproj/Localizable.strings index 7ad385fcbb..7a273fa86e 100644 --- a/Riot/Assets/fr.lproj/Localizable.strings +++ b/Riot/Assets/fr.lproj/Localizable.strings @@ -54,3 +54,4 @@ "MSG_FROM_USER_IN_ROOM_TITLE" = "%@ dans %@"; /* Sticker from a specific person, not referencing a room. */ "STICKER_FROM_USER" = "%@ a envoyé un sticker"; +"KEY_VERIFICATION_REQUEST_FROM_USER" = "%@ veut vérifier"; From 9410d4cf70fe0eccb8ea166266be5d142659f410 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Fri, 17 Jan 2020 15:35:02 +0000 Subject: [PATCH 084/282] Translated using Weblate (Hungarian) Currently translated at 100.0% (29 of 29 strings) Translation: Riot iOS/Riot iOS (Push) Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios-push/hu/ --- Riot/Assets/hu.lproj/Localizable.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Assets/hu.lproj/Localizable.strings b/Riot/Assets/hu.lproj/Localizable.strings index 1b800160b4..7ca6c4d4a4 100644 --- a/Riot/Assets/hu.lproj/Localizable.strings +++ b/Riot/Assets/hu.lproj/Localizable.strings @@ -54,3 +54,4 @@ "MSG_FROM_USER_IN_ROOM_TITLE" = "%@ in %@ -ban/ben"; /* Sticker from a specific person, not referencing a room. */ "STICKER_FROM_USER" = "%@ matricát küldött"; +"KEY_VERIFICATION_REQUEST_FROM_USER" = "%@ ellenőrizni szeretné"; From e482b9fb145ee7ec390717245adbac520311a0c0 Mon Sep 17 00:00:00 2001 From: Osoitz Date: Sat, 18 Jan 2020 11:42:24 +0000 Subject: [PATCH 085/282] Translated using Weblate (Basque) Currently translated at 100.0% (863 of 863 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/eu/ --- Riot/Assets/eu.lproj/Vector.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Assets/eu.lproj/Vector.strings b/Riot/Assets/eu.lproj/Vector.strings index ca88973a34..8c4eed7a13 100644 --- a/Riot/Assets/eu.lproj/Vector.strings +++ b/Riot/Assets/eu.lproj/Vector.strings @@ -941,3 +941,4 @@ "key_verification_tile_request_incoming_approval_decline" = "Ukatu"; "key_verification_tile_conclusion_done_title" = "Egiaztatuta"; "key_verification_tile_conclusion_warning_title" = "Fidagarritasun gabeko saio hasiera"; +"key_verification_incoming_request_incoming_alert_message" = "%@(e)k egiaztatu nahi du"; From 9aef7ccdc80277fc84422eedfd6316a9a7380c7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Sat, 18 Jan 2020 07:38:50 +0000 Subject: [PATCH 086/282] Translated using Weblate (French) Currently translated at 100.0% (863 of 863 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/fr/ --- Riot/Assets/fr.lproj/Vector.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index 57943357de..c85ff08531 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -956,3 +956,4 @@ "key_verification_tile_request_incoming_approval_decline" = "Refuser"; "key_verification_tile_conclusion_done_title" = "Vérifié"; "key_verification_tile_conclusion_warning_title" = "Connexion non approuvée"; +"key_verification_incoming_request_incoming_alert_message" = "%@ veut vérifier"; From 33e77ab54f7c1be5f2e51c254495519d84f17555 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Fri, 17 Jan 2020 15:35:21 +0000 Subject: [PATCH 087/282] Translated using Weblate (Hungarian) Currently translated at 100.0% (863 of 863 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/hu/ --- Riot/Assets/hu.lproj/Vector.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index d81a7508bb..08d9e3abb3 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -958,3 +958,4 @@ "key_verification_tile_request_incoming_approval_decline" = "Elutasít"; "key_verification_tile_conclusion_done_title" = "Hitelesített"; "key_verification_tile_conclusion_warning_title" = "Megbízhatatlan belépés"; +"key_verification_incoming_request_incoming_alert_message" = "%@ ellenőrizni szeretné"; From dfedd60bc0ae2e9a71958d6a08c5d3d0aba34ab1 Mon Sep 17 00:00:00 2001 From: random Date: Mon, 20 Jan 2020 11:09:51 +0000 Subject: [PATCH 088/282] Translated using Weblate (Italian) Currently translated at 100.0% (863 of 863 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/it/ --- Riot/Assets/it.lproj/Vector.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Assets/it.lproj/Vector.strings b/Riot/Assets/it.lproj/Vector.strings index f0348542e4..2cbb8ad83e 100644 --- a/Riot/Assets/it.lproj/Vector.strings +++ b/Riot/Assets/it.lproj/Vector.strings @@ -928,3 +928,4 @@ "key_verification_tile_request_incoming_approval_decline" = "Rifiuta"; "key_verification_tile_conclusion_done_title" = "Verificato"; "key_verification_tile_conclusion_warning_title" = "Accesso non fidato"; +"key_verification_incoming_request_incoming_alert_message" = "%@ vuole verificare"; From d041597421ffbb79cb50b58d67c9dc1989d845ed Mon Sep 17 00:00:00 2001 From: random Date: Mon, 20 Jan 2020 11:10:11 +0000 Subject: [PATCH 089/282] Translated using Weblate (Italian) Currently translated at 100.0% (29 of 29 strings) Translation: Riot iOS/Riot iOS (Push) Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios-push/it/ --- Riot/Assets/it.lproj/Localizable.strings | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Riot/Assets/it.lproj/Localizable.strings b/Riot/Assets/it.lproj/Localizable.strings index daa2aefdd6..78d7e2fba6 100644 --- a/Riot/Assets/it.lproj/Localizable.strings +++ b/Riot/Assets/it.lproj/Localizable.strings @@ -1,5 +1,5 @@ /* New message from a specific person, not referencing a room */ -"MSG_FROM_USER" = "Messaggio ricevuto da %@"; +"MSG_FROM_USER" = "%@ ha inviato un messaggio"; /* New message from a specific person in a named room */ "MSG_FROM_USER_IN_ROOM" = "%@ ha scritto in %@"; /* New message from a specific person, not referencing a room. Content included. */ @@ -11,7 +11,7 @@ /* New action message from a specific person in a named room. */ "ACTION_FROM_USER_IN_ROOM" = "%@: * %@ %@"; /* New action message from a specific person, not referencing a room. */ -"IMAGE_FROM_USER" = "%@ ha inviato un'immagine %@"; +"IMAGE_FROM_USER" = "%@ ha invitato un'immagine %@"; /* New action message from a specific person in a named room. */ "IMAGE_FROM_USER_IN_ROOM" = "%@ ha inviato un'immagine %@ in %@"; /* A single unread message in a room */ @@ -50,3 +50,8 @@ "VOICE_CONF_NAMED_FROM_USER" = "Chiamata di gruppo da %@: '%@'"; /* Incoming named video conference invite from a specific person */ "VIDEO_CONF_NAMED_FROM_USER" = "Video chiamata di gruppo da %@: '%@'"; +/* Message title for a specific person in a named room */ +"MSG_FROM_USER_IN_ROOM_TITLE" = "%@ in %@"; +/* Sticker from a specific person, not referencing a room. */ +"STICKER_FROM_USER" = "%@ ha inviato un adesivo"; +"KEY_VERIFICATION_REQUEST_FROM_USER" = "%@ vuole verificare"; From 927a909f21ce016c2b1992f164fe538e2400f147 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 22 Jan 2020 16:21:47 +0100 Subject: [PATCH 090/282] RoomDataSource: Handle room members trust level for an encrypted room. --- .../Modules/Room/DataSources/RoomDataSource.h | 23 ++++ .../Modules/Room/DataSources/RoomDataSource.m | 104 ++++++++++++++++++ 2 files changed, 127 insertions(+) diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.h b/Riot/Modules/Room/DataSources/RoomDataSource.h index fd01e5ad69..ab09e2d2f5 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.h +++ b/Riot/Modules/Room/DataSources/RoomDataSource.h @@ -19,6 +19,18 @@ #import "WidgetManager.h" +/** + RoomEncryptionTrustLevel represents the room members trust level in an encrypted room. + */ +typedef NS_ENUM(NSUInteger, RoomEncryptionTrustLevel) { + RoomEncryptionTrustLevelTrusted, + RoomEncryptionTrustLevelWarning, + RoomEncryptionTrustLevelNormal, + RoomEncryptionTrustLevelUnknown +}; + +@protocol RoomDataSourceDelegate; + /** The data source for `RoomViewController` in Vector. */ @@ -39,6 +51,11 @@ */ @property(nonatomic) BOOL showBubbleDateTimeOnSelection; +/** + Current room members trust level for an encrypted room. + */ +@property(nonatomic, readonly) RoomEncryptionTrustLevel encryptionTrustLevel; + /** Check if there is an active jitsi widget in the room and return it. @@ -85,3 +102,9 @@ failure:(void(^)(NSError*))failure; @end + +@protocol RoomDataSourceDelegate + +- (void)roomDataSource:(RoomDataSource*)roomDataSource didUpdateEncryptionTrustLevel:(RoomEncryptionTrustLevel)roomEncryptionTrustLevel; + +@end diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m index 280eef50d3..0b1ffbed99 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -44,6 +44,10 @@ @interface RoomDataSource() // Timer used to debounce cells refresh @property (nonatomic, strong) NSTimer *refreshCellsTimer; +@property (nonatomic, readonly) id roomDataSourceDelegate; + +@property(nonatomic, readwrite) RoomEncryptionTrustLevel encryptionTrustLevel; + @end @implementation RoomDataSource @@ -83,6 +87,9 @@ - (instancetype)initWithRoomId:(NSString *)roomId andMatrixSession:(MXSession *) [self registerKeyVerificationRequestNotification]; [self registerDeviceVerificationTransactionNotification]; + [self registerTrustLevelDidChangeNotifications]; + + self.encryptionTrustLevel = RoomEncryptionTrustLevelUnknown; } return self; } @@ -105,6 +112,21 @@ - (void)finalizeInitialization NSLog(@"[MXKRoomDataSource] finalizeRoomDataSource: Cannot retrieve all room members"); }]; } + + if (self.room.summary.isEncrypted) + { + [self fetchEncryptionTrustedLevel]; + } +} + +- (id)roomDataSourceDelegate +{ + if (!self.delegate || ![self.delegate conformsToProtocol:@protocol(RoomDataSourceDelegate)]) + { + return nil; + } + + return ((id)(self.delegate)); } - (void)updateEventFormatter @@ -167,6 +189,88 @@ - (void)setNeedsUpdateAdditionalContentHeightForCellData:(id= 1.0) + { + roomEncryptionTrustLevel = RoomEncryptionTrustLevelTrusted; + } + else if (trustedMembersPercentage == 0.0) + { + roomEncryptionTrustLevel = RoomEncryptionTrustLevelNormal; + } + else + { + roomEncryptionTrustLevel = RoomEncryptionTrustLevelWarning; + } + + self.encryptionTrustLevel = roomEncryptionTrustLevel; + [self.roomDataSourceDelegate roomDataSource:self didUpdateEncryptionTrustLevel:roomEncryptionTrustLevel]; + + } failure:^(NSError *error) { + NSLog(@"[RoomDataSource] trustLevelDidChangeRelatedToUserId fails to retrieve room members trusted progress"); + }]; + } + + } failure:^(NSError *error) { + NSLog(@"[RoomDataSource] trustLevelDidChangeRelatedToUserId fails to retrieve room members"); + }]; +} + #pragma mark - - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section From 39e841963eaa8385cc33889f2fb8cf70586b8335 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 22 Jan 2020 16:23:39 +0100 Subject: [PATCH 091/282] ExpandedRoomTitleView: Add badge image view on room avatar. --- .../Title/Expanded/ExpandedRoomTitleView.h | 1 + .../Title/Expanded/ExpandedRoomTitleView.xib | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Riot/Modules/Room/Views/Title/Expanded/ExpandedRoomTitleView.h b/Riot/Modules/Room/Views/Title/Expanded/ExpandedRoomTitleView.h index ff6a1b40d2..e22e0422cd 100644 --- a/Riot/Modules/Room/Views/Title/Expanded/ExpandedRoomTitleView.h +++ b/Riot/Modules/Room/Views/Title/Expanded/ExpandedRoomTitleView.h @@ -19,6 +19,7 @@ @interface ExpandedRoomTitleView : RoomTitleView @property (weak, nonatomic) IBOutlet MXKImageView *roomAvatar; +@property (weak, nonatomic) IBOutlet UIImageView *roomAvatarBadgeImageView; @property (weak, nonatomic) IBOutlet UIView *roomAvatarHeaderBackground; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *roomAvatarHeaderBackgroundHeightConstraint; diff --git a/Riot/Modules/Room/Views/Title/Expanded/ExpandedRoomTitleView.xib b/Riot/Modules/Room/Views/Title/Expanded/ExpandedRoomTitleView.xib index 353189cdd7..537c5168f9 100644 --- a/Riot/Modules/Room/Views/Title/Expanded/ExpandedRoomTitleView.xib +++ b/Riot/Modules/Room/Views/Title/Expanded/ExpandedRoomTitleView.xib @@ -1,11 +1,11 @@ - + - + @@ -27,11 +27,20 @@ + + + + + + + + + @@ -148,6 +157,7 @@ + @@ -155,10 +165,11 @@ + - - + + From 69a04c1af82bf62d838465d39b5b9ed18d669235 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 22 Jan 2020 16:25:39 +0100 Subject: [PATCH 092/282] Room messages: Handle encryption shields decoration. --- .../Encryption/RoomEncryptedDataBubbleCell.m | 87 +++++++++---------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m index 009654230d..bd022f4309 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m +++ b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m @@ -23,60 +23,55 @@ @implementation RoomEncryptedDataBubbleCell + (UIImage*)encryptionIconForEvent:(MXEvent*)event andSession:(MXSession*)session { - NSString *encryptionIcon; + MXRoom *room = [session roomWithRoomId:event.roomId]; + BOOL isRoomEncrypted = room.summary.isEncrypted && session.crypto; + + if (!isRoomEncrypted) + { + return nil; + } + + NSString *encryptionIconName; + UIImage* encryptionIcon; if (!event.isEncrypted) { - encryptionIcon = @"e2e_unencrypted"; - if (event.isLocalEvent - || event.contentHasBeenEdited) // Local echo for an edit is clear but uses a true event id, the one of the edited event + || event.contentHasBeenEdited) // Local echo for an edit is clear but uses a true event id, the one of the edited event { // Patch: Display the verified icon by default on pending outgoing messages in the encrypted rooms when the encryption is enabled - MXRoom *room = [session roomWithRoomId:event.roomId]; - if (room.summary.isEncrypted && session.crypto) - { - // The outgoing message are encrypted by default - encryptionIcon = @"e2e_verified"; - } + // The outgoing message are encrypted by default + encryptionIconName = nil; + } + else + { + encryptionIconName = @"encryption_warning"; } } else if (event.decryptionError) { - encryptionIcon = @"e2e_blocked"; + encryptionIconName = @"encryption_warning"; } else { MXDeviceInfo *deviceInfo = [session.crypto eventDeviceInfo:event]; - if (deviceInfo) + if (deviceInfo.trustLevel.isVerified) { - switch (deviceInfo.trustLevel.localVerificationStatus) - { - case MXDeviceUnknown: - case MXDeviceUnverified: - { - encryptionIcon = @"e2e_warning"; - break; - } - case MXDeviceVerified: - { - encryptionIcon = @"e2e_verified"; - break; - } - default: - break; - } + encryptionIconName = nil; + } + else + { + encryptionIconName = @"encryption_warning"; } } - if (!encryptionIcon) + if (encryptionIconName) { - // Use the warning icon by default - encryptionIcon = @"e2e_warning"; + encryptionIcon = [UIImage imageNamed:encryptionIconName]; } - return [UIImage imageNamed:encryptionIcon]; + return encryptionIcon; } + (void)addEncryptionStatusFromBubbleData:(MXKRoomBubbleCellData *)bubbleData inContainerView:(UIView *)containerView @@ -104,19 +99,23 @@ + (void)addEncryptionStatusFromBubbleData:(MXKRoomBubbleCellData *)bubbleData in } UIImage *icon = [RoomEncryptedDataBubbleCell encryptionIconForEvent:component.event andSession:bubbleData.mxSession]; - UIImageView *encryptStatusImageView = [[UIImageView alloc] initWithImage:icon]; - - CGRect frame = encryptStatusImageView.frame; - frame.origin.y = component.position.y + 3; - encryptStatusImageView.frame = frame; - CGPoint center = encryptStatusImageView.center; - center.x = containerView.frame.size.width / 2; - encryptStatusImageView.center = center; - - encryptStatusImageView.tag = componentIndex; - - [containerView addSubview:encryptStatusImageView]; + if (icon) + { + UIImageView *encryptStatusImageView = [[UIImageView alloc] initWithImage:icon]; + + CGRect frame = encryptStatusImageView.frame; + frame.origin.y = component.position.y + 3; + encryptStatusImageView.frame = frame; + + CGPoint center = encryptStatusImageView.center; + center.x = containerView.frame.size.width / 2; + encryptStatusImageView.center = center; + + encryptStatusImageView.tag = componentIndex; + + [containerView addSubview:encryptStatusImageView]; + } } } From 2ab9a4ae83516b121bf655d3765e172141706d49 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 22 Jan 2020 16:27:04 +0100 Subject: [PATCH 093/282] RoomVC: Handle encryption shields decoration for composer and expanded header. --- Riot/Modules/Room/RoomViewController.m | 85 +++++++++++++++++-- .../Views/InputToolbar/RoomInputToolbarView.m | 4 - 2 files changed, 77 insertions(+), 12 deletions(-) diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 4053321bef..9f58642a85 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -125,7 +125,8 @@ @interface RoomViewController () + ReactionHistoryCoordinatorBridgePresenterDelegate, CameraPresenterDelegate, MediaPickerCoordinatorBridgePresenterDelegate, + RoomDataSourceDelegate> { // The expanded header ExpandedRoomTitleView *expandedHeader; @@ -1421,6 +1422,11 @@ - (BOOL)isRoomPreview return NO; } +- (BOOL)isEncryptionEnabled +{ + return self.roomDataSource.room.summary.isEncrypted && self.mainSession.crypto != nil; +} + - (void)refreshRoomTitle { if (rightBarButtonItems && !self.navigationItem.rightBarButtonItems) @@ -1541,12 +1547,8 @@ - (void)refreshRoomInputToolbar roomInputToolbarView.supportCallOption &= ([[AppDelegate theDelegate] callStatusBarWindow] == nil); } - // Check whether the encryption is enabled in the room - if (self.roomDataSource.room.summary.isEncrypted) - { - // Encrypt the user's messages as soon as the user supports the encryption? - roomInputToolbarView.isEncryptionEnabled = (self.mainSession.crypto != nil); - } + // Update encryption decoration if needed + [self updateEncryptionDecorationForRoomInputToolbar:roomInputToolbarView]; } else if (self.inputToolbarView && [self.inputToolbarView isKindOfClass:DisabledRoomInputToolbarView.class]) { @@ -1626,6 +1628,61 @@ - (void)updateInputToolBarViewHeight [UIView setAnimationsEnabled:YES]; } +- (UIImage*)roomEncryptionBadgeImage +{ + NSString *encryptionIconName; + UIImage *encryptionIcon; + + if (self.isEncryptionEnabled) + { + RoomEncryptionTrustLevel roomEncryptionTrustLevel = ((RoomDataSource*)self.roomDataSource).encryptionTrustLevel; + + switch (roomEncryptionTrustLevel) { + case RoomEncryptionTrustLevelWarning: + encryptionIconName = @"encryption_warning"; + break; + case RoomEncryptionTrustLevelNormal: + encryptionIconName = @"encryption_normal"; + break; + case RoomEncryptionTrustLevelTrusted: + encryptionIconName = @"encryption_trusted"; + break; + default: + break; + } + } + + if (encryptionIconName) + { + encryptionIcon = [UIImage imageNamed:encryptionIconName]; + } + + return encryptionIcon; +} + +- (void)updateInputToolbarEncryptionDecoration +{ + if (self.inputToolbarView && [self.inputToolbarView isKindOfClass:RoomInputToolbarView.class]) + { + RoomInputToolbarView *roomInputToolbarView = (RoomInputToolbarView*)self.inputToolbarView; + [self updateEncryptionDecorationForRoomInputToolbar:roomInputToolbarView]; + } +} + +- (void)updateExpandedHeaderEncryptionDecoration +{ + if (self->expandedHeader) + { + self->expandedHeader.roomAvatarBadgeImageView.image = self.roomEncryptionBadgeImage; + } +} + +- (void)updateEncryptionDecorationForRoomInputToolbar:(RoomInputToolbarView*)roomInputToolbarView +{ + roomInputToolbarView.isEncryptionEnabled = self.isEncryptionEnabled; + roomInputToolbarView.encryptedRoomIcon.image = self.roomEncryptionBadgeImage; +} + - (void)handleLongPressFromCell:(id)cell withTappedEvent:(MXEvent*)event { if (event && !customizedRoomDataSource.selectedEventId) @@ -1725,9 +1782,12 @@ - (void)showExpandedHeader:(BOOL)isVisible // Note the avatar title view does not define tap gesture. expandedHeader.roomAvatar.alpha = 0.0; + expandedHeader.roomAvatarBadgeImageView.alpha = 0.0; shadowImage = [[UIImage alloc] init]; + [self updateExpandedHeaderEncryptionDecoration]; + // Dismiss the keyboard when header is expanded. [self.inputToolbarView dismissKeyboard]; } @@ -1758,7 +1818,8 @@ - (void)showExpandedHeader:(BOOL)isVisible self.bubblesTableViewTopConstraint.constant = (isVisible ? self.expandedHeaderContainerHeightConstraint.constant - self.bubblesTableView.mxk_adjustedContentInset.top : 0); self.jumpToLastUnreadBannerContainerTopConstraint.constant = (isVisible ? self.expandedHeaderContainerHeightConstraint.constant : self.bubblesTableView.mxk_adjustedContentInset.top); - expandedHeader.roomAvatar.alpha = 1; + self->expandedHeader.roomAvatar.alpha = 1; + self->expandedHeader.roomAvatarBadgeImageView.alpha = 1; // Force to render the view [self forceLayoutRefresh]; @@ -3193,6 +3254,14 @@ - (RoomInputToolbarView*)inputToolbarViewAsRoomInputToolbarView return roomInputToolbarView; } +#pragma mark - RoomDataSourceDelegate + +- (void)roomDataSource:(RoomDataSource *)roomDataSource didUpdateEncryptionTrustLevel:(RoomEncryptionTrustLevel)roomEncryptionTrustLevel +{ + [self updateInputToolbarEncryptionDecoration]; + [self updateExpandedHeaderEncryptionDecoration]; +} + #pragma mark - Segues - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m index 855c21454a..233ccab1dd 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m @@ -128,8 +128,6 @@ - (void)setIsEncryptionEnabled:(BOOL)isEncryptionEnabled if (_isEncryptionEnabled) { - self.encryptedRoomIcon.image = [UIImage imageNamed:@"e2e_verified"]; - // Check the device screen size before using large placeholder if ([GBDeviceInfo deviceInfo].family == GBDeviceFamilyiPad || [GBDeviceInfo deviceInfo].displayInfo.display >= GBDeviceDisplay4p7Inch) { @@ -138,8 +136,6 @@ - (void)setIsEncryptionEnabled:(BOOL)isEncryptionEnabled } else { - self.encryptedRoomIcon.image = [UIImage imageNamed:@"e2e_unencrypted"]; - // Check the device screen size before using large placeholder if ([GBDeviceInfo deviceInfo].family == GBDeviceFamilyiPad || [GBDeviceInfo deviceInfo].displayInfo.display >= GBDeviceDisplay4p7Inch) { From 90da9b38687ea020d669cffb1a8f0340ba72d26d Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 22 Jan 2020 16:45:33 +0100 Subject: [PATCH 094/282] Update changes. --- CHANGES.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 3349c7ebfa..d65df8187b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,10 @@ +Changes in 0.10.5 (2020-xx-xx) +=============================================== + +Improvements: + * ON/OFF Cross-signing development in a Lab setting (#2855). + * RoomVC: Update encryption decoration with shields (#2934, #2930, #2906). + Changes in 0.10.4 (2019-12-11) =============================================== From d2e93bb9aa1cbef7bd096dc61e47fd4dc8f450dc Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 23 Jan 2020 16:37:23 +0100 Subject: [PATCH 095/282] RoomDataSource: Now compute encryption trust level from trusted devices percentage in room. --- Riot/Modules/Room/DataSources/RoomDataSource.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m index 0b1ffbed99..24da3dcebe 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -238,18 +238,18 @@ - (void)encryptionTrustLevelDidChangeRelatedToUserId:(NSString*)userId // If user belongs to the room refresh the trust level if (roomMember) - { - [self.room trustedMembersProgressWithSuccess:^(NSProgress *trustedMembersProgress) { + { + [self.room trustLevelSummaryWithSuccess:^(MXUsersTrustLevelSummary *usersTrustLevelSummary) { RoomEncryptionTrustLevel roomEncryptionTrustLevel; - double trustedMembersPercentage = trustedMembersProgress.fractionCompleted; + double trustedDevicesPercentage = usersTrustLevelSummary.trustedDevicesProgress.fractionCompleted; - if (trustedMembersPercentage >= 1.0) + if (trustedDevicesPercentage >= 1.0) { roomEncryptionTrustLevel = RoomEncryptionTrustLevelTrusted; } - else if (trustedMembersPercentage == 0.0) + else if (trustedDevicesPercentage == 0.0) { roomEncryptionTrustLevel = RoomEncryptionTrustLevelNormal; } From a7df696f5b0ac6ce7d44154e21d855643de43f73 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 23 Jan 2020 16:39:33 +0100 Subject: [PATCH 096/282] RoomDataSource: Fix refactoring. --- Riot/Modules/Room/DataSources/RoomDataSource.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m index 24da3dcebe..3bd0d0165f 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -239,7 +239,7 @@ - (void)encryptionTrustLevelDidChangeRelatedToUserId:(NSString*)userId // If user belongs to the room refresh the trust level if (roomMember) { - [self.room trustLevelSummaryWithSuccess:^(MXUsersTrustLevelSummary *usersTrustLevelSummary) { + [self.room membersTrustLevelSummaryWithSuccess:^(MXUsersTrustLevelSummary *usersTrustLevelSummary) { RoomEncryptionTrustLevel roomEncryptionTrustLevel; From b096c5a37f7c1fbc860eb053f3ef3022fa7a9fa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Mesk=C3=B3?= Date: Thu, 23 Jan 2020 10:19:06 +0000 Subject: [PATCH 097/282] Translated using Weblate (Hungarian) Currently translated at 100.0% (863 of 863 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/hu/ --- Riot/Assets/hu.lproj/Vector.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index 08d9e3abb3..19ecc7002e 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -13,7 +13,7 @@ "leave" = "Elhagyás"; "remove" = "Eltávolítás"; "invite" = "Meghívás"; -"retry" = "Újrapróbál"; +"retry" = "Újra"; "cancel" = "Mégse"; "save" = "Ment"; // Room Details From 5480e913adbcd0abc5fdb3dc0370b269651e46a7 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 24 Jan 2020 17:13:08 +0100 Subject: [PATCH 098/282] RoomVC: Use encryption normal shield when retrieving room encryption trust level. --- Riot/Modules/Room/RoomViewController.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 9f58642a85..7da406e14a 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -1647,6 +1647,9 @@ - (UIImage*)roomEncryptionBadgeImage case RoomEncryptionTrustLevelTrusted: encryptionIconName = @"encryption_trusted"; break; + case RoomEncryptionTrustLevelUnknown: + encryptionIconName = @"encryption_normal"; + break; default: break; } From 2525eed3110387fb57686241d2fe113e353eb1c3 Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Sat, 25 Jan 2020 16:43:31 +0000 Subject: [PATCH 099/282] Translated using Weblate (Albanian) Currently translated at 100.0% (29 of 29 strings) Translation: Riot iOS/Riot iOS (Push) Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios-push/sq/ --- Riot/Assets/sq.lproj/Localizable.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Assets/sq.lproj/Localizable.strings b/Riot/Assets/sq.lproj/Localizable.strings index d060cfcd87..40a5a18ebc 100644 --- a/Riot/Assets/sq.lproj/Localizable.strings +++ b/Riot/Assets/sq.lproj/Localizable.strings @@ -54,3 +54,4 @@ "MSG_FROM_USER_IN_ROOM_TITLE" = "%@ në %@"; /* Sticker from a specific person, not referencing a room. */ "STICKER_FROM_USER" = "%@ dërgoi një ngjitës"; +"KEY_VERIFICATION_REQUEST_FROM_USER" = "%@ dëshiron të verifikojë"; From d3c42ef314ae8d8ac0840d6a3932a9f85adde400 Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Sat, 25 Jan 2020 16:43:02 +0000 Subject: [PATCH 100/282] Translated using Weblate (Albanian) Currently translated at 99.8% (861 of 863 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/sq/ --- Riot/Assets/sq.lproj/Vector.strings | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Riot/Assets/sq.lproj/Vector.strings b/Riot/Assets/sq.lproj/Vector.strings index e07ff2de99..5734610564 100644 --- a/Riot/Assets/sq.lproj/Vector.strings +++ b/Riot/Assets/sq.lproj/Vector.strings @@ -935,3 +935,19 @@ "service_terms_modal_policy_checkbox_accessibility_hint" = "I vini shenjë që të pranohet %@"; "settings_labs_dm_key_verification" = "Verifikim kyçesh përmes mesazhi të drejtpërdrejtë"; "settings_labs_cross_signing" = "Nënshkrim kryq"; +"settings_labs" = "LABS"; +"settings_calls_stun_server_fallback_button" = "Lejoni shërbyes rrugëzgjidhje asistimi thirrjesh"; +"settings_calls_stun_server_fallback_description" = "Lejoni shërbyes rrugëzgjidhje asistimi thirrjesh %@, kur shërbyesi juaj Home nuk ofron një të tillë (gjatë thirrjes, adresa juaj IP do t’i bëhet e ditur)"; +"key_verification_tile_request_incoming_title" = "Kërkesë verifikimi"; +"key_verification_tile_request_outgoing_title" = "Email-i i verifikimit u dërgua"; +"key_verification_tile_request_status_data_loading" = "Ngarkim të dhënat…"; +"key_verification_tile_request_status_waiting" = "Në pritje…"; +"key_verification_tile_request_status_expired" = "I skaduar"; +"key_verification_tile_request_status_cancelled_by_me" = "Anuluat"; +"key_verification_tile_request_status_cancelled" = "%@ u anulua"; +"key_verification_tile_request_status_accepted" = "Pranuat"; +"key_verification_tile_request_incoming_approval_accept" = "Pranoje"; +"key_verification_tile_request_incoming_approval_decline" = "Hidhe poshtë"; +"key_verification_tile_conclusion_done_title" = "I verifikuar"; +"key_verification_tile_conclusion_warning_title" = "Hyrje jo e besuar në"; +"key_verification_incoming_request_incoming_alert_message" = "%s dëshiron të verifikojë"; From 597e29b80ec1afa3d2e47af49cb2968d7ec02278 Mon Sep 17 00:00:00 2001 From: Osoitz Date: Sat, 25 Jan 2020 12:26:23 +0000 Subject: [PATCH 101/282] Translated using Weblate (Basque) Currently translated at 100.0% (863 of 863 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/eu/ --- Riot/Assets/eu.lproj/Vector.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/Assets/eu.lproj/Vector.strings b/Riot/Assets/eu.lproj/Vector.strings index 8c4eed7a13..7ed7ef4cce 100644 --- a/Riot/Assets/eu.lproj/Vector.strings +++ b/Riot/Assets/eu.lproj/Vector.strings @@ -68,7 +68,7 @@ "auth_missing_phone" = "Telefono zenbakia falta da"; "auth_missing_email_or_phone" = "E-mail helbidea edo telefono zenbakia falta da"; "auth_password_dont_match" = "Pasahitzak ez datoz bat"; -"auth_forgot_password" = "Pasahitza ahaztu duzu?"; +"auth_forgot_password" = "Pasahitza ahaztuta?"; "auth_use_server_options" = "Erabili zerbitzari pertsonalizatuaren ezarpenak (aurreratua)"; "auth_email_validation_message" = "Egiaztatu zure e-mail helbidea erregistroarekin jarraitzeko"; "auth_recaptcha_message" = "Hasiera-zerbitzari honek robot bat ez zarela egiaztatu nahi du"; From 54a9ab88b8d8a23bdc8bf4e1b0d0563285fbca6d Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 27 Jan 2020 17:30:09 +0100 Subject: [PATCH 102/282] Settings: Remove "End-to-End Encryption" from the LABS section #2941 --- CHANGES.rst | 1 + .../Modules/Settings/SettingsViewController.m | 125 ------------------ 2 files changed, 1 insertion(+), 125 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index d65df8187b..bbe790a135 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,7 @@ Changes in 0.10.5 (2020-xx-xx) Improvements: * ON/OFF Cross-signing development in a Lab setting (#2855). * RoomVC: Update encryption decoration with shields (#2934, #2930, #2906). + * Settings: Remove "End-to-End Encryption" from the LABS section (#2941). Changes in 0.10.4 (2019-12-11) =============================================== diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index 4a6fe80889..ac1f27318d 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -136,7 +136,6 @@ { LABS_USE_ROOM_MEMBERS_LAZY_LOADING_INDEX = 0, LABS_USE_JITSI_WIDGET_INDEX, - LABS_CRYPTO_INDEX, LABS_COUNT, // TODO: Remove it once features exist LABS_DM_KEY_VERIFICATION_INDEX, LABS_CROSS_SIGNING_INDEX, @@ -2428,26 +2427,6 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleJitsiForConference:) forControlEvents:UIControlEventTouchUpInside]; - cell = labelAndSwitchCell; - } - else if (row == LABS_CRYPTO_INDEX) - { - MXSession* session = [AppDelegate theDelegate].mxSessions[0]; - - MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - - labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_labs_e2e_encryption", @"Vector", nil); - labelAndSwitchCell.mxkSwitch.on = (nil != session.crypto); - labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; - - [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleLabsEndToEndEncryption:) forControlEvents:UIControlEventTouchUpInside]; - - if (session.crypto) - { - // Once crypto is enabled, it is enabled - labelAndSwitchCell.mxkSwitch.enabled = NO; - } - cell = labelAndSwitchCell; } else if (row == LABS_DM_KEY_VERIFICATION_INDEX) @@ -3420,110 +3399,6 @@ - (void)toggleJitsiForConference:(id)sender [self.tableView reloadData]; } } - -- (void)toggleLabsEndToEndEncryption:(id)sender -{ - if (sender && [sender isKindOfClass:UISwitch.class]) - { - UISwitch *switchButton = (UISwitch*)sender; - MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; - - if (switchButton.isOn && !account.mxCredentials.deviceId.length) - { - // Prompt the user to log in again when no device id is available. - __weak typeof(self) weakSelf = self; - - // Prompt user - NSString *msg = NSLocalizedStringFromTable(@"settings_labs_e2e_encryption_prompt_message", @"Vector", nil); - - [currentAlert dismissViewControllerAnimated:NO completion:nil]; - - currentAlert = [UIAlertController alertControllerWithTitle:nil message:msg preferredStyle:UIAlertControllerStyleAlert]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"later"] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - } - - // Reset toggle button - [switchButton setOn:NO animated:YES]; - - }]]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - - switchButton.enabled = NO; - [self startActivityIndicator]; - - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - - [[AppDelegate theDelegate] logoutWithConfirmation:NO completion:nil]; - - }); - } - - }]]; - - [currentAlert mxk_setAccessibilityIdentifier:@"SettingsVCEnableEncryptionAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; - } - else - { - [self startActivityIndicator]; - - MXSession* session = [AppDelegate theDelegate].mxSessions[0]; - [session enableCrypto:switchButton.isOn success:^{ - - // When disabling crypto, reset the current device id as it cannot be reused. - // This means that the user will need to log in again if he wants to re-enable e2e. - if (!switchButton.isOn) - { - [account resetDeviceId]; - } - - // Reload all data source of encrypted rooms - MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:session]; - - for (MXRoom *room in session.rooms) - { - if (room.summary.isEncrypted) - { - [roomDataSourceManager roomDataSourceForRoom:room.roomId create:NO onComplete:^(MXKRoomDataSource *roomDataSource) { - [roomDataSource reload]; - }]; - } - } - - // Once crypto is enabled, it is enabled - switchButton.enabled = NO; - - [self stopActivityIndicator]; - - // Refresh table view to add cryptography information. - [self.tableView reloadData]; - - } failure:^(NSError *error) { - - [self stopActivityIndicator]; - - // Come back to previous state button - [switchButton setOn:!switchButton.isOn animated:YES]; - }]; - } - } -} - (void)toggleLabsDMKeyVerification:(id)sender { From 7b24865be47e40cc1e93726863e8012f1f543988 Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 28 Jan 2020 14:33:56 +0100 Subject: [PATCH 103/282] Room creation: Follow SDK changes (#2945) Room creation: Follow SDK changes --- Riot/AppDelegate.m | 60 +++++----- .../OnBoarding/OnBoardingManager.swift | 3 +- .../AuthenticationViewController.m | 20 +--- .../Details/ContactDetailsViewController.m | 106 ++++++++---------- .../StartChat/StartChatViewController.m | 57 +++++----- 5 files changed, 110 insertions(+), 136 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 85db64b6fb..18234d6de2 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -3840,38 +3840,34 @@ - (void)createDirectChatWithUserId:(NSString*)userId completion:(void (^)(void)) { // Create a new room by inviting the other user only if it is defined and not oneself NSArray *invite = ((userId && ![mxSession.myUser.userId isEqualToString:userId]) ? @[userId] : nil); - - [mxSession createRoom:nil - visibility:kMXRoomDirectoryVisibilityPrivate - roomAlias:nil - topic:nil - invite:invite - invite3PID:nil - isDirect:(invite.count != 0) - preset:kMXRoomPresetTrustedPrivateChat - success:^(MXRoom *room) { - - // Open created room - [self showRoom:room.roomId andEventId:nil withMatrixSession:mxSession]; - - if (completion) - { - completion(); - } - - } - failure:^(NSError *error) { - - NSLog(@"[AppDelegate] Create direct chat failed"); - //Alert user - [self showErrorAsAlert:error]; - - if (completion) - { - completion(); - } - - }]; + + MXRoomCreationParameters *roomCreationParameters = [MXRoomCreationParameters new]; + roomCreationParameters.visibility = kMXRoomDirectoryVisibilityPrivate; + roomCreationParameters.inviteArray = invite; + roomCreationParameters.isDirect = (invite.count != 0); + roomCreationParameters.preset = kMXRoomPresetTrustedPrivateChat; + + [mxSession createRoomWithParameters:roomCreationParameters success:^(MXRoom *room) { + + // Open created room + [self showRoom:room.roomId andEventId:nil withMatrixSession:mxSession]; + + if (completion) + { + completion(); + } + + } failure:^(NSError *error) { + + NSLog(@"[AppDelegate] Create direct chat failed"); + //Alert user + [self showErrorAsAlert:error]; + + if (completion) + { + completion(); + } + }]; } else if (completion) { diff --git a/Riot/Managers/OnBoarding/OnBoardingManager.swift b/Riot/Managers/OnBoarding/OnBoardingManager.swift index 9656fb2136..f224383d47 100644 --- a/Riot/Managers/OnBoarding/OnBoardingManager.swift +++ b/Riot/Managers/OnBoarding/OnBoardingManager.swift @@ -56,7 +56,8 @@ final public class OnBoardingManager: NSObject { case .success: // Create DM room with Riot-bot - let httpOperation = self.session.createRoom(name: nil, visibility: .private, alias: nil, topic: nil, invite: [Constants.riotBotMatrixId], invite3PID: nil, isDirect: true, preset: .trustedPrivateChat) { (response) in + let roomCreationParameters = MXRoomCreationParameters(forDirectRoomWithUser: Constants.riotBotMatrixId) + let httpOperation = self.session.createRoom(parameters: roomCreationParameters) { (response) in switch response { case .success: diff --git a/Riot/Modules/Authentication/AuthenticationViewController.m b/Riot/Modules/Authentication/AuthenticationViewController.m index 2ba4215450..b0a604f803 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.m +++ b/Riot/Modules/Authentication/AuthenticationViewController.m @@ -1109,21 +1109,11 @@ - (void)authenticationViewController:(MXKAuthenticationViewController *)authenti if (self.authType == MXKAuthenticationTypeRegister) { MXKAccount *account = [[MXKAccountManager sharedManager] accountForUserId:userId]; - - [account.mxSession createRoom:nil - visibility:kMXRoomDirectoryVisibilityPrivate - roomAlias:nil - topic:nil - invite:@[@"@riot-bot:matrix.org"] - invite3PID:nil - isDirect:YES - preset:kMXRoomPresetTrustedPrivateChat - success:nil - failure:^(NSError *error) { - - NSLog(@"[AuthenticationVC] Create chat with riot-bot failed"); - - }]; + + MXRoomCreationParameters *roomCreationParameters = [MXRoomCreationParameters parametersForDirectRoomWithUser:@"@riot-bot:matrix.org"]; + [account.mxSession createRoomWithParameters:roomCreationParameters success:nil failure:^(NSError *error) { + NSLog(@"[AuthenticationVC] Create chat with riot-bot failed"); + }]; } // Remove auth view controller on successful login diff --git a/Riot/Modules/Contacts/Details/ContactDetailsViewController.m b/Riot/Modules/Contacts/Details/ContactDetailsViewController.m index f8fc01553f..a2106226e8 100644 --- a/Riot/Modules/Contacts/Details/ContactDetailsViewController.m +++ b/Riot/Modules/Contacts/Details/ContactDetailsViewController.m @@ -1042,35 +1042,31 @@ - (void)onActionButtonPressed:(id)sender } // Create a new room - roomCreationRequest = [self.mainSession createRoom:nil - visibility:kMXRoomDirectoryVisibilityPrivate - roomAlias:nil - topic:nil - invite:inviteArray - invite3PID:invite3PIDArray - isDirect:YES - preset:kMXRoomPresetTrustedPrivateChat - success:^(MXRoom *room) { - - roomCreationRequest = nil; - - [self removePendingActionMask]; - - [[AppDelegate theDelegate] showRoom:room.roomId andEventId:nil withMatrixSession:self.mainSession]; - - } - failure:^(NSError *error) { - - NSLog(@"[ContactDetailsViewController] Create room failed"); - - roomCreationRequest = nil; - - [self removePendingActionMask]; - - // Notify user - [[AppDelegate theDelegate] showErrorAsAlert:error]; - - }]; + MXRoomCreationParameters *roomCreationParameters = [MXRoomCreationParameters new]; + roomCreationParameters.visibility = kMXRoomDirectoryVisibilityPrivate; + roomCreationParameters.inviteArray = inviteArray; + roomCreationParameters.invite3PIDArray = invite3PIDArray; + roomCreationParameters.isDirect = YES; + roomCreationParameters.preset = kMXRoomPresetTrustedPrivateChat; + roomCreationRequest = [self.mainSession createRoomWithParameters:roomCreationParameters success:^(MXRoom *room) { + + roomCreationRequest = nil; + + [self removePendingActionMask]; + + [[AppDelegate theDelegate] showRoom:room.roomId andEventId:nil withMatrixSession:self.mainSession]; + + } failure:^(NSError *error) { + + NSLog(@"[ContactDetailsViewController] Create room failed"); + + roomCreationRequest = nil; + + [self removePendingActionMask]; + + // Notify user + [[AppDelegate theDelegate] showErrorAsAlert:error]; + }]; } break; } @@ -1093,36 +1089,28 @@ - (void)onActionButtonPressed:(id)sender else { // Create a new room - roomCreationRequest = [self.mainSession createRoom:nil - visibility:kMXRoomDirectoryVisibilityPrivate - roomAlias:nil - topic:nil - invite:@[matrixId] - invite3PID:nil - isDirect:YES - preset:kMXRoomPresetTrustedPrivateChat - success:^(MXRoom *room) { - - roomCreationRequest = nil; - - // Delay the call in order to be sure that the room is ready - dispatch_async(dispatch_get_main_queue(), ^{ - [room placeCallWithVideo:isVideoCall success:nil failure:nil]; - [self removePendingActionMask]; - }); - - } failure:^(NSError *error) { - - NSLog(@"[ContactDetailsViewController] Create room failed"); - - roomCreationRequest = nil; - - [self removePendingActionMask]; - - // Notify user - [[AppDelegate theDelegate] showErrorAsAlert:error]; - - }]; + MXRoomCreationParameters *roomCreationParameters = [MXRoomCreationParameters parametersForDirectRoomWithUser:matrixId]; + roomCreationRequest = [self.mainSession createRoomWithParameters:roomCreationParameters success:^(MXRoom *room) { + + roomCreationRequest = nil; + + // Delay the call in order to be sure that the room is ready + dispatch_async(dispatch_get_main_queue(), ^{ + [room placeCallWithVideo:isVideoCall success:nil failure:nil]; + [self removePendingActionMask]; + }); + + } failure:^(NSError *error) { + + NSLog(@"[ContactDetailsViewController] Create room failed"); + + roomCreationRequest = nil; + + [self removePendingActionMask]; + + // Notify user + [[AppDelegate theDelegate] showErrorAsAlert:error]; + }]; } break; } diff --git a/Riot/Modules/StartChat/StartChatViewController.m b/Riot/Modules/StartChat/StartChatViewController.m index 2c8a952266..9c77482440 100644 --- a/Riot/Modules/StartChat/StartChatViewController.m +++ b/Riot/Modules/StartChat/StartChatViewController.m @@ -573,35 +573,34 @@ - (IBAction)onButtonPressed:(id)sender MXRoomPreset preset = (isDirect ? kMXRoomPresetTrustedPrivateChat : nil); // Create new room - roomCreationRequest = [self.mainSession createRoom:nil - visibility:kMXRoomDirectoryVisibilityPrivate - roomAlias:nil - topic:nil - invite:(inviteArray.count ? inviteArray : nil) - invite3PID:(invite3PIDArray.count ? invite3PIDArray : nil) - isDirect:isDirect - preset:preset - success:^(MXRoom *room) { - - roomCreationRequest = nil; - - [self stopActivityIndicator]; - - [[AppDelegate theDelegate] showRoom:room.roomId andEventId:nil withMatrixSession:self.mainSession]; - - } failure:^(NSError *error) { - - createBarButtonItem.enabled = YES; - - roomCreationRequest = nil; - [self stopActivityIndicator]; - - NSLog(@"[StartChatViewController] Create room failed"); - - // Alert user - [[AppDelegate theDelegate] showErrorAsAlert:error]; - - }]; + MXRoomCreationParameters *roomCreationParameters = [MXRoomCreationParameters new]; + roomCreationParameters.visibility = kMXRoomDirectoryVisibilityPrivate; + roomCreationParameters.inviteArray = inviteArray.count ? inviteArray : nil; + roomCreationParameters.invite3PIDArray = invite3PIDArray.count ? invite3PIDArray : nil; + roomCreationParameters.isDirect = isDirect; + roomCreationParameters.preset = preset; + + roomCreationRequest = [self.mainSession createRoomWithParameters:roomCreationParameters success:^(MXRoom *room) { + + roomCreationRequest = nil; + + [self stopActivityIndicator]; + + [[AppDelegate theDelegate] showRoom:room.roomId andEventId:nil withMatrixSession:self.mainSession]; + + } failure:^(NSError *error) { + + createBarButtonItem.enabled = YES; + + roomCreationRequest = nil; + [self stopActivityIndicator]; + + NSLog(@"[StartChatViewController] Create room failed"); + + // Alert user + [[AppDelegate theDelegate] showErrorAsAlert:error]; + + }]; } } else if (sender == self.navigationItem.leftBarButtonItem) From 485e66569c9610f08a14487fa726ccf38c0578f0 Mon Sep 17 00:00:00 2001 From: Joshua Dietz Date: Mon, 27 Jan 2020 17:44:06 +0000 Subject: [PATCH 104/282] Translated using Weblate (German) Currently translated at 100.0% (29 of 29 strings) Translation: Riot iOS/Riot iOS (Push) Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios-push/de/ --- Riot/Assets/de.lproj/Localizable.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Assets/de.lproj/Localizable.strings b/Riot/Assets/de.lproj/Localizable.strings index bcadaeb5f7..3c8e1644ad 100644 --- a/Riot/Assets/de.lproj/Localizable.strings +++ b/Riot/Assets/de.lproj/Localizable.strings @@ -54,3 +54,4 @@ "MSG_FROM_USER_IN_ROOM_TITLE" = "%@ in %@"; /* Sticker from a specific person, not referencing a room. */ "STICKER_FROM_USER" = "%@ hat einen Sticker gesendet"; +"KEY_VERIFICATION_REQUEST_FROM_USER" = "%@ möchte verifizieren"; From 12ec147b62c848906293d901fc35a012963a57b2 Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 28 Jan 2020 15:10:00 +0100 Subject: [PATCH 105/282] Negotiate E2E by default for DMs #2943 --- Riot/AppDelegate.m | 48 ++++++++++------ Riot/Categories/MXSession+Riot.h | 14 +++++ Riot/Categories/MXSession+Riot.m | 34 +++++++++++ .../Details/ContactDetailsViewController.m | 56 +++++++++++++------ .../StartChat/StartChatViewController.m | 54 +++++++++++------- 5 files changed, 151 insertions(+), 55 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 18234d6de2..ddde73e322 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -3841,33 +3841,45 @@ - (void)createDirectChatWithUserId:(NSString*)userId completion:(void (^)(void)) // Create a new room by inviting the other user only if it is defined and not oneself NSArray *invite = ((userId && ![mxSession.myUser.userId isEqualToString:userId]) ? @[userId] : nil); - MXRoomCreationParameters *roomCreationParameters = [MXRoomCreationParameters new]; - roomCreationParameters.visibility = kMXRoomDirectoryVisibilityPrivate; - roomCreationParameters.inviteArray = invite; - roomCreationParameters.isDirect = (invite.count != 0); - roomCreationParameters.preset = kMXRoomPresetTrustedPrivateChat; - - [mxSession createRoomWithParameters:roomCreationParameters success:^(MXRoom *room) { - - // Open created room - [self showRoom:room.roomId andEventId:nil withMatrixSession:mxSession]; + void (^onFailure)(NSError *) = ^(NSError *error){ + NSLog(@"[AppDelegate] Create direct chat failed"); + //Alert user + [self showErrorAsAlert:error]; if (completion) { completion(); } + }; - } failure:^(NSError *error) { - - NSLog(@"[AppDelegate] Create direct chat failed"); - //Alert user - [self showErrorAsAlert:error]; + [mxSession canEnableE2EByDefaultInNewRoomWithUsers:invite success:^(BOOL canEnableE2E) { + + MXRoomCreationParameters *roomCreationParameters = [MXRoomCreationParameters new]; + roomCreationParameters.visibility = kMXRoomDirectoryVisibilityPrivate; + roomCreationParameters.inviteArray = invite; + roomCreationParameters.isDirect = (invite.count != 0); + roomCreationParameters.preset = kMXRoomPresetTrustedPrivateChat; - if (completion) + if (canEnableE2E) { - completion(); + roomCreationParameters.initialStateEvents = @[ + [MXRoomCreationParameters initialStateEventForEncryptionWithAlgorithm:kMXCryptoMegolmAlgorithm + ]]; } - }]; + + [mxSession createRoomWithParameters:roomCreationParameters success:^(MXRoom *room) { + + // Open created room + [self showRoom:room.roomId andEventId:nil withMatrixSession:mxSession]; + + if (completion) + { + completion(); + } + + } failure:onFailure]; + + } failure:onFailure]; } else if (completion) { diff --git a/Riot/Categories/MXSession+Riot.h b/Riot/Categories/MXSession+Riot.h index 9bc296a980..b907e64a18 100644 --- a/Riot/Categories/MXSession+Riot.h +++ b/Riot/Categories/MXSession+Riot.h @@ -25,4 +25,18 @@ */ - (NSUInteger)riot_missedDiscussionsCount; +/** + Decide if E2E must be enabled in a new room with a list users + + @param userIds the list of users; + + @param success A block object called when the operation succeeds. + @param failure A block object called when the operation fails. + + @return a MXHTTPOperation instance. + */ +- (MXHTTPOperation*)canEnableE2EByDefaultInNewRoomWithUsers:(NSArray*)userIds + success:(void (^)(BOOL canEnableE2E))success + failure:(void (^)(NSError *error))failure; + @end diff --git a/Riot/Categories/MXSession+Riot.m b/Riot/Categories/MXSession+Riot.m index 3d8805b5e3..1281f4db41 100644 --- a/Riot/Categories/MXSession+Riot.m +++ b/Riot/Categories/MXSession+Riot.m @@ -17,6 +17,7 @@ #import "MXSession+Riot.h" #import "MXRoom+Riot.h" +#import "Riot-Swift.h" @implementation MXSession (Riot) @@ -48,4 +49,37 @@ - (NSUInteger)riot_missedDiscussionsCount return missedDiscussionsCount; } +- (MXHTTPOperation*)canEnableE2EByDefaultInNewRoomWithUsers:(NSArray*)userIds + success:(void (^)(BOOL canEnableE2E))success + failure:(void (^)(NSError *error))failure +{ + MXHTTPOperation *operation; + if (RiotSettings.shared.enableCrossSigning) + { + // Check whether all users have uploaded device keys before. + // If so, encryption can be enabled in the new room + operation = [self.crypto downloadKeys:userIds forceDownload:NO success:^(MXUsersDevicesMap *usersDevicesInfoMap, NSDictionary *crossSigningKeysMap) { + + BOOL allUsersHaveDeviceKeys = YES; + for (NSString *userId in userIds) + { + if ([usersDevicesInfoMap deviceIdsForUser:userId].count == 0) + { + allUsersHaveDeviceKeys = NO; + break; + } + } + + success(allUsersHaveDeviceKeys); + + } failure:failure]; + } + else + { + success(NO); + } + + return operation; +} + @end diff --git a/Riot/Modules/Contacts/Details/ContactDetailsViewController.m b/Riot/Modules/Contacts/Details/ContactDetailsViewController.m index a2106226e8..a6c9684967 100644 --- a/Riot/Modules/Contacts/Details/ContactDetailsViewController.m +++ b/Riot/Modules/Contacts/Details/ContactDetailsViewController.m @@ -20,6 +20,7 @@ #import "AppDelegate.h" #import "Riot-Swift.h" +#import "MXSession+Riot.h" #import "RoomMemberTitleView.h" @@ -1040,33 +1041,52 @@ - (void)onActionButtonPressed:(id)sender { inviteArray = @[participantId]; } - - // Create a new room - MXRoomCreationParameters *roomCreationParameters = [MXRoomCreationParameters new]; - roomCreationParameters.visibility = kMXRoomDirectoryVisibilityPrivate; - roomCreationParameters.inviteArray = inviteArray; - roomCreationParameters.invite3PIDArray = invite3PIDArray; - roomCreationParameters.isDirect = YES; - roomCreationParameters.preset = kMXRoomPresetTrustedPrivateChat; - roomCreationRequest = [self.mainSession createRoomWithParameters:roomCreationParameters success:^(MXRoom *room) { - - roomCreationRequest = nil; - - [self removePendingActionMask]; - [[AppDelegate theDelegate] showRoom:room.roomId andEventId:nil withMatrixSession:self.mainSession]; - - } failure:^(NSError *error) { + MXWeakify(self); + void (^onFailure)(NSError *) = ^(NSError *error){ + MXStrongifyAndReturnIfNil(self); NSLog(@"[ContactDetailsViewController] Create room failed"); - roomCreationRequest = nil; + self->roomCreationRequest = nil; [self removePendingActionMask]; // Notify user [[AppDelegate theDelegate] showErrorAsAlert:error]; - }]; + }; + + + // Create a new room + [self.mainSession canEnableE2EByDefaultInNewRoomWithUsers:inviteArray success:^(BOOL canEnableE2E) { + MXStrongifyAndReturnIfNil(self); + + MXRoomCreationParameters *roomCreationParameters = [MXRoomCreationParameters new]; + roomCreationParameters.visibility = kMXRoomDirectoryVisibilityPrivate; + roomCreationParameters.inviteArray = inviteArray; + roomCreationParameters.invite3PIDArray = invite3PIDArray; + roomCreationParameters.isDirect = YES; + roomCreationParameters.preset = kMXRoomPresetTrustedPrivateChat; + + if (canEnableE2E && roomCreationParameters.invite3PIDArray == nil) + { + roomCreationParameters.initialStateEvents = @[ + [MXRoomCreationParameters initialStateEventForEncryptionWithAlgorithm:kMXCryptoMegolmAlgorithm + ]]; + } + + + self->roomCreationRequest = [self.mainSession createRoomWithParameters:roomCreationParameters success:^(MXRoom *room) { + + self->roomCreationRequest = nil; + + [self removePendingActionMask]; + + [[AppDelegate theDelegate] showRoom:room.roomId andEventId:nil withMatrixSession:self.mainSession]; + + } failure:onFailure]; + + } failure:onFailure]; } break; } diff --git a/Riot/Modules/StartChat/StartChatViewController.m b/Riot/Modules/StartChat/StartChatViewController.m index 9c77482440..806919e9d9 100644 --- a/Riot/Modules/StartChat/StartChatViewController.m +++ b/Riot/Modules/StartChat/StartChatViewController.m @@ -19,6 +19,7 @@ #import "AppDelegate.h" #import "Riot-Swift.h" +#import "MXSession+Riot.h" @interface StartChatViewController () { @@ -571,36 +572,51 @@ - (IBAction)onButtonPressed:(id)sender { // Ensure direct chat are created with equal ops on both sides (the trusted_private_chat preset) MXRoomPreset preset = (isDirect ? kMXRoomPresetTrustedPrivateChat : nil); - - // Create new room - MXRoomCreationParameters *roomCreationParameters = [MXRoomCreationParameters new]; - roomCreationParameters.visibility = kMXRoomDirectoryVisibilityPrivate; - roomCreationParameters.inviteArray = inviteArray.count ? inviteArray : nil; - roomCreationParameters.invite3PIDArray = invite3PIDArray.count ? invite3PIDArray : nil; - roomCreationParameters.isDirect = isDirect; - roomCreationParameters.preset = preset; - roomCreationRequest = [self.mainSession createRoomWithParameters:roomCreationParameters success:^(MXRoom *room) { + MXWeakify(self); + void (^onFailure)(NSError *) = ^(NSError *error){ + MXStrongifyAndReturnIfNil(self); - roomCreationRequest = nil; + self->createBarButtonItem.enabled = YES; + self->roomCreationRequest = nil; [self stopActivityIndicator]; - [[AppDelegate theDelegate] showRoom:room.roomId andEventId:nil withMatrixSession:self.mainSession]; + NSLog(@"[StartChatViewController] Create room failed"); - } failure:^(NSError *error) { + // Alert user + [[AppDelegate theDelegate] showErrorAsAlert:error]; + }; - createBarButtonItem.enabled = YES; + [self.mainSession canEnableE2EByDefaultInNewRoomWithUsers:inviteArray success:^(BOOL canEnableE2E) { + MXStrongifyAndReturnIfNil(self); - roomCreationRequest = nil; - [self stopActivityIndicator]; + // Create new room + MXRoomCreationParameters *roomCreationParameters = [MXRoomCreationParameters new]; + roomCreationParameters.visibility = kMXRoomDirectoryVisibilityPrivate; + roomCreationParameters.inviteArray = inviteArray.count ? inviteArray : nil; + roomCreationParameters.invite3PIDArray = invite3PIDArray.count ? invite3PIDArray : nil; + roomCreationParameters.isDirect = isDirect; + roomCreationParameters.preset = preset; - NSLog(@"[StartChatViewController] Create room failed"); + if (canEnableE2E && roomCreationParameters.invite3PIDArray == nil) + { + roomCreationParameters.initialStateEvents = @[ + [MXRoomCreationParameters initialStateEventForEncryptionWithAlgorithm:kMXCryptoMegolmAlgorithm + ]]; + } - // Alert user - [[AppDelegate theDelegate] showErrorAsAlert:error]; + self->roomCreationRequest = [self.mainSession createRoomWithParameters:roomCreationParameters success:^(MXRoom *room) { - }]; + self->roomCreationRequest = nil; + + [self stopActivityIndicator]; + + [[AppDelegate theDelegate] showRoom:room.roomId andEventId:nil withMatrixSession:self.mainSession]; + + } failure:onFailure]; + + } failure:onFailure]; } } else if (sender == self.navigationItem.leftBarButtonItem) From 589c1ca3b641472e8a19c30f4188666a17aea192 Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 28 Jan 2020 15:53:09 +0100 Subject: [PATCH 106/282] Message decoration: Do not decorate state events #2947 --- .../Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m index bd022f4309..a306c644b4 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m +++ b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m @@ -37,10 +37,9 @@ + (UIImage*)encryptionIconForEvent:(MXEvent*)event andSession:(MXSession*)sessio if (!event.isEncrypted) { if (event.isLocalEvent + || event.isState || event.contentHasBeenEdited) // Local echo for an edit is clear but uses a true event id, the one of the edited event { - // Patch: Display the verified icon by default on pending outgoing messages in the encrypted rooms when the encryption is enabled - // The outgoing message are encrypted by default encryptionIconName = nil; } else From a669f35fc66db495ca602b41003b0d6b940525eb Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 28 Jan 2020 16:51:03 +0100 Subject: [PATCH 107/282] Settings: LABS: Keep only one flag for cross-signing --- Riot/Assets/en.lproj/Vector.strings | 3 +-- Riot/Generated/Strings.swift | 12 +++------ Riot/Managers/Settings/RiotSettings.swift | 8 ------ .../Modules/Settings/SettingsViewController.m | 26 +++---------------- 4 files changed, 8 insertions(+), 41 deletions(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index a18e8f8cee..863d245812 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -458,8 +458,7 @@ "settings_labs_room_members_lazy_loading_error_message" = "Your homeserver does not support lazy loading of room members yet. Try later."; "settings_labs_create_conference_with_jitsi" = "Create conference calls with jitsi"; "settings_labs_message_reaction" = "React to messages with emoji"; -"settings_labs_dm_key_verification" = "Key verification by direct message"; -"settings_labs_cross_signing" = "Cross-Signing"; +"settings_labs_enable_cross_signing" = "Enable cross-signing to verify per-user instead of per-device (in development)"; "settings_version" = "Version %@"; "settings_olm_version" = "Olm Version %@"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index d2028bb4ee..119a293e25 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -3022,14 +3022,6 @@ internal enum VectorL10n { internal static var settingsLabsCreateConferenceWithJitsi: String { return VectorL10n.tr("Vector", "settings_labs_create_conference_with_jitsi") } - /// Cross-Signing - internal static var settingsLabsCrossSigning: String { - return VectorL10n.tr("Vector", "settings_labs_cross_signing") - } - /// Key verification by direct message - internal static var settingsLabsDmKeyVerification: String { - return VectorL10n.tr("Vector", "settings_labs_dm_key_verification") - } /// End-to-End Encryption internal static var settingsLabsE2eEncryption: String { return VectorL10n.tr("Vector", "settings_labs_e2e_encryption") @@ -3038,6 +3030,10 @@ internal enum VectorL10n { internal static var settingsLabsE2eEncryptionPromptMessage: String { return VectorL10n.tr("Vector", "settings_labs_e2e_encryption_prompt_message") } + /// Enable cross-signing to verify per-user instead of per-device (in development) + internal static var settingsLabsEnableCrossSigning: String { + return VectorL10n.tr("Vector", "settings_labs_enable_cross_signing") + } /// React to messages with emoji internal static var settingsLabsMessageReaction: String { return VectorL10n.tr("Vector", "settings_labs_message_reaction") diff --git a/Riot/Managers/Settings/RiotSettings.swift b/Riot/Managers/Settings/RiotSettings.swift index b980968496..9d351658de 100644 --- a/Riot/Managers/Settings/RiotSettings.swift +++ b/Riot/Managers/Settings/RiotSettings.swift @@ -123,14 +123,6 @@ final class RiotSettings: NSObject { UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.createConferenceCallsWithJitsi) } } - - var enableDMKeyVerification: Bool { - get { - return UserDefaults.standard.bool(forKey: UserDefaultsKeys.enableDMKeyVerification) - } set { - UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.enableDMKeyVerification) - } - } var enableCrossSigning: Bool { get { diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index ac1f27318d..ca9430d3bf 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -136,10 +136,8 @@ { LABS_USE_ROOM_MEMBERS_LAZY_LOADING_INDEX = 0, LABS_USE_JITSI_WIDGET_INDEX, - LABS_COUNT, // TODO: Remove it once features exist - LABS_DM_KEY_VERIFICATION_INDEX, LABS_CROSS_SIGNING_INDEX, -// LABS_COUNT + LABS_COUNT }; enum { @@ -2429,25 +2427,14 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N cell = labelAndSwitchCell; } - else if (row == LABS_DM_KEY_VERIFICATION_INDEX) - { - MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - - labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_labs_dm_key_verification", @"Vector", nil); - labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.enableDMKeyVerification; - labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; - - [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleLabsDMKeyVerification:) forControlEvents:UIControlEventTouchUpInside]; - - cell = labelAndSwitchCell; - } else if (row == LABS_CROSS_SIGNING_INDEX) { MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_labs_cross_signing", @"Vector", nil); + labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_labs_enable_cross_signing", @"Vector", nil); labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.enableCrossSigning; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + labelAndSwitchCell.mxkSwitch.enabled = YES; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleLabsCrossSigning:) forControlEvents:UIControlEventTouchUpInside]; @@ -3400,13 +3387,6 @@ - (void)toggleJitsiForConference:(id)sender } } -- (void)toggleLabsDMKeyVerification:(id)sender -{ - UISwitch *switchButton = (UISwitch*)sender; - - RiotSettings.shared.enableDMKeyVerification = switchButton.isOn; -} - - (void)toggleLabsCrossSigning:(id)sender { UISwitch *switchButton = (UISwitch*)sender; From c75a9e54c1277e046ecbcc6df35b75e3cef6c516 Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 28 Jan 2020 18:18:19 +0100 Subject: [PATCH 108/282] version++ --- CHANGES.rst | 2 +- Riot/SupportingFiles/Info.plist | 4 ++-- RiotShareExtension/SupportingFiles/Info.plist | 4 ++-- SiriIntents/Info.plist | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index bbe790a135..2c9f973b5a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,4 +1,4 @@ -Changes in 0.10.5 (2020-xx-xx) +Changes in 0.11.0 (2020-xx-xx) =============================================== Improvements: diff --git a/Riot/SupportingFiles/Info.plist b/Riot/SupportingFiles/Info.plist index d44b69a656..bc9a7c9c81 100644 --- a/Riot/SupportingFiles/Info.plist +++ b/Riot/SupportingFiles/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.10.4 + 0.11.0 CFBundleSignature ???? CFBundleVersion - 0.10.4 + 0.11.0 ITSAppUsesNonExemptEncryption ITSEncryptionExportComplianceCode diff --git a/RiotShareExtension/SupportingFiles/Info.plist b/RiotShareExtension/SupportingFiles/Info.plist index 3306a7f7be..7d92e33096 100644 --- a/RiotShareExtension/SupportingFiles/Info.plist +++ b/RiotShareExtension/SupportingFiles/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 0.10.4 + 0.11.0 CFBundleVersion - 0.10.4 + 0.11.0 NSExtension NSExtensionAttributes diff --git a/SiriIntents/Info.plist b/SiriIntents/Info.plist index 6d300ab1a1..ff6a9ad9d9 100644 --- a/SiriIntents/Info.plist +++ b/SiriIntents/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 0.10.4 + 0.11.0 CFBundleVersion - 0.10.4 + 0.11.0 NSExtension NSExtensionAttributes From 6c8e5e31276af23444461d1de741ce6883440c75 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Tue, 28 Jan 2020 16:10:47 +0000 Subject: [PATCH 109/282] Translated using Weblate (Hungarian) Currently translated at 100.0% (862 of 862 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/hu/ --- Riot/Assets/hu.lproj/Vector.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index 19ecc7002e..b654549b91 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -959,3 +959,4 @@ "key_verification_tile_conclusion_done_title" = "Hitelesített"; "key_verification_tile_conclusion_warning_title" = "Megbízhatatlan belépés"; "key_verification_incoming_request_incoming_alert_message" = "%@ ellenőrizni szeretné"; +"settings_labs_enable_cross_signing" = "Kereszt-aláírás engedélyezése a felhasználó alapú azonosításhoz az eszköz alapú helyett (fejlesztés alatt)"; From ae3674b8ed388fd9bebbf6216d60dc563d3fcae5 Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 28 Jan 2020 21:09:51 +0100 Subject: [PATCH 110/282] Settings: Add a dedicated screen for Security --- Riot.xcodeproj/project.pbxproj | 18 + .../Settings/Security/Security.storyboard | 41 + .../Security/SecurityViewController.h | 28 + .../Security/SecurtiyViewController.m | 4825 +++++++++++++++++ .../Modules/Settings/SettingsViewController.m | 43 +- Tools/SwiftGen/swiftgen-config.yml | 4 +- 6 files changed, 4956 insertions(+), 3 deletions(-) create mode 100644 Riot/Modules/Settings/Security/Security.storyboard create mode 100644 Riot/Modules/Settings/Security/SecurityViewController.h create mode 100644 Riot/Modules/Settings/Security/SecurtiyViewController.m diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index fb93d2e5f7..f000c7d32f 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -78,6 +78,8 @@ 32891D712264DF7B00C82226 /* DeviceVerificationVerifiedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32891D6F2264DF7B00C82226 /* DeviceVerificationVerifiedViewController.swift */; }; 32891D75226728EE00C82226 /* DeviceVerificationDataLoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32891D73226728EE00C82226 /* DeviceVerificationDataLoadingViewController.swift */; }; 32891D76226728EF00C82226 /* DeviceVerificationDataLoadingViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 32891D74226728EE00C82226 /* DeviceVerificationDataLoadingViewController.storyboard */; }; + 3291DC8A23E0BE820009732F /* Security.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3291DC8923E0BE820009732F /* Security.storyboard */; }; + 3291DC8D23E0BFF10009732F /* SecurtiyViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3291DC8C23E0BFF10009732F /* SecurtiyViewController.m */; }; 329E746622CD02EA006F9797 /* BubbleReactionActionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 329E746422CD02EA006F9797 /* BubbleReactionActionViewCell.xib */; }; 329E746722CD02EA006F9797 /* BubbleReactionActionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 329E746522CD02EA006F9797 /* BubbleReactionActionViewCell.swift */; }; 32A6001622C661100042C1D9 /* EditHistoryViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32A6000D22C661100042C1D9 /* EditHistoryViewState.swift */; }; @@ -746,6 +748,9 @@ 32891D6F2264DF7B00C82226 /* DeviceVerificationVerifiedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationVerifiedViewController.swift; sourceTree = ""; }; 32891D73226728EE00C82226 /* DeviceVerificationDataLoadingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceVerificationDataLoadingViewController.swift; sourceTree = ""; }; 32891D74226728EE00C82226 /* DeviceVerificationDataLoadingViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = DeviceVerificationDataLoadingViewController.storyboard; sourceTree = ""; }; + 3291DC8923E0BE820009732F /* Security.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Security.storyboard; sourceTree = ""; }; + 3291DC8B23E0BFF10009732F /* SecurityViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SecurityViewController.h; sourceTree = ""; }; + 3291DC8C23E0BFF10009732F /* SecurtiyViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SecurtiyViewController.m; sourceTree = ""; }; 329E746422CD02EA006F9797 /* BubbleReactionActionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BubbleReactionActionViewCell.xib; sourceTree = ""; }; 329E746522CD02EA006F9797 /* BubbleReactionActionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BubbleReactionActionViewCell.swift; sourceTree = ""; }; 32A6000D22C661100042C1D9 /* EditHistoryViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditHistoryViewState.swift; sourceTree = ""; }; @@ -1803,6 +1808,16 @@ path = Loading; sourceTree = ""; }; + 3291DC8823E0BE380009732F /* Security */ = { + isa = PBXGroup; + children = ( + 3291DC8B23E0BFF10009732F /* SecurityViewController.h */, + 3291DC8C23E0BFF10009732F /* SecurtiyViewController.m */, + 3291DC8923E0BE820009732F /* Security.storyboard */, + ); + path = Security; + sourceTree = ""; + }; 32935CB21F628B98006888C8 /* js */ = { isa = PBXGroup; children = ( @@ -2527,6 +2542,7 @@ B1B5567B20EE6C4C00210D55 /* Language */, B1B5567820EE6C4C00210D55 /* PhoneCountry */, B1B5568020EE6C4C00210D55 /* DeactivateAccount */, + 3291DC8823E0BE380009732F /* Security */, B1CE9EFB22148681000FAE6A /* SignOut */, ); path = Settings; @@ -4194,6 +4210,7 @@ B110872421F098F0003554A5 /* ActivityIndicatorView.xib in Resources */, B1B5573320EE6C4D00210D55 /* GroupHomeViewController.xib in Resources */, 3232AB2122564D9100AD6A5C /* README.md in Resources */, + 3291DC8A23E0BE820009732F /* Security.storyboard in Resources */, B1B5593920EF7BAC00210D55 /* TableViewCellWithCheckBoxes.xib in Resources */, B1B557C120EF5B4500210D55 /* DisabledRoomInputToolbarView.xib in Resources */, B1DCC61722E5E17100625807 /* EmojiPickerViewController.storyboard in Resources */, @@ -4578,6 +4595,7 @@ B1B558D420EF768F00210D55 /* RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.m in Sources */, B169331420F3CAFC00746532 /* PublicRoomTableViewCell.m in Sources */, 32BF995721FB07A400698084 /* SettingsKeyBackupTableViewSection.swift in Sources */, + 3291DC8D23E0BFF10009732F /* SecurtiyViewController.m in Sources */, B1C45A88232A8C2600165425 /* SettingsIdentityServerViewState.swift in Sources */, B14F142F22144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewModelType.swift in Sources */, 32DB557822FDADE50016329E /* ServiceTermsModalScreenViewState.swift in Sources */, diff --git a/Riot/Modules/Settings/Security/Security.storyboard b/Riot/Modules/Settings/Security/Security.storyboard new file mode 100644 index 0000000000..aed7941023 --- /dev/null +++ b/Riot/Modules/Settings/Security/Security.storyboard @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Settings/Security/SecurityViewController.h b/Riot/Modules/Settings/Security/SecurityViewController.h new file mode 100644 index 0000000000..673f015432 --- /dev/null +++ b/Riot/Modules/Settings/Security/SecurityViewController.h @@ -0,0 +1,28 @@ +/* + Copyright 2019 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 + +#import "DeviceView.h" + +#import "MediaPickerViewController.h" + +@interface SecurityViewController : MXKTableViewController + ++ (SecurityViewController*)instantiateWithMatrixSession:(MXSession*)matrixSession; + +@end + diff --git a/Riot/Modules/Settings/Security/SecurtiyViewController.m b/Riot/Modules/Settings/Security/SecurtiyViewController.m new file mode 100644 index 0000000000..5844d146f9 --- /dev/null +++ b/Riot/Modules/Settings/Security/SecurtiyViewController.m @@ -0,0 +1,4825 @@ +/* + Copyright 2019 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 "SecurityViewController.h" + +#import + +#import + +#import "AppDelegate.h" +#import "AvatarGenerator.h" + +#import "BugReportViewController.h" + +#import "WebViewViewController.h" + +#import "CountryPickerViewController.h" +#import "LanguagePickerViewController.h" +#import "DeactivateAccountViewController.h" + +#import "NBPhoneNumberUtil.h" +#import "RageShakeManager.h" +#import "ThemeService.h" +#import "TableViewCellWithPhoneNumberTextField.h" + +#import "GroupsDataSource.h" +#import "GroupTableViewCellWithSwitch.h" + +#import "GBDeviceInfo_iOS.h" + +#import "Riot-Swift.h" + +NSString* const kSettingsViewControllerPhoneBookCountryCellId2 = @"kSettingsViewControllerPhoneBookCountryCellId2"; + +enum +{ + SETTINGS_SECTION_SIGN_OUT_INDEX = 0, + SETTINGS_SECTION_USER_SETTINGS_INDEX, + SETTINGS_SECTION_NOTIFICATIONS_SETTINGS_INDEX, + SETTINGS_SECTION_CALLS_INDEX, + SETTINGS_SECTION_DISCOVERY_INDEX, + SETTINGS_SECTION_IDENTITY_SERVER_INDEX, + SETTINGS_SECTION_CONTACTS_INDEX, + SETTINGS_SECTION_IGNORED_USERS_INDEX, + SETTINGS_SECTION_INTEGRATIONS_INDEX, + SETTINGS_SECTION_USER_INTERFACE_INDEX, + SETTINGS_SECTION_ADVANCED_INDEX, + SETTINGS_SECTION_OTHER_INDEX, + SETTINGS_SECTION_LABS_INDEX, + SETTINGS_SECTION_CRYPTOGRAPHY_INDEX, + SETTINGS_SECTION_KEYBACKUP_INDEX, + SETTINGS_SECTION_DEVICES_INDEX, + SETTINGS_SECTION_FLAIR_INDEX, + SETTINGS_SECTION_DEACTIVATE_ACCOUNT_INDEX, + SETTINGS_SECTION_COUNT +}; + +enum +{ + NOTIFICATION_SETTINGS_ENABLE_PUSH_INDEX = 0, + NOTIFICATION_SETTINGS_SHOW_DECODED_CONTENT, + NOTIFICATION_SETTINGS_GLOBAL_SETTINGS_INDEX, + NOTIFICATION_SETTINGS_PIN_MISSED_NOTIFICATIONS_INDEX, + NOTIFICATION_SETTINGS_PIN_UNREAD_INDEX, + //NOTIFICATION_SETTINGS_CONTAINING_MY_USER_NAME_INDEX, + //NOTIFICATION_SETTINGS_CONTAINING_MY_DISPLAY_NAME_INDEX, + //NOTIFICATION_SETTINGS_SENT_TO_ME_INDEX, + //NOTIFICATION_SETTINGS_INVITED_TO_ROOM_INDEX, + //NOTIFICATION_SETTINGS_PEOPLE_LEAVE_JOIN_INDEX, + //NOTIFICATION_SETTINGS_CALL_INVITATION_INDEX, + NOTIFICATION_SETTINGS_COUNT +}; + +enum +{ + CALLS_ENABLE_CALLKIT_INDEX = 0, + CALLS_CALLKIT_DESCRIPTION_INDEX, + CALLS_ENABLE_STUN_SERVER_FALLBACK_INDEX, + CALLS_STUN_SERVER_FALLBACK_DESCRIPTION_INDEX, + CALLS_COUNT +}; + +enum +{ + INTEGRATIONS_INDEX, + INTEGRATIONS_DESCRIPTION_INDEX, + INTEGRATIONS_COUNT +}; + +enum +{ + USER_INTERFACE_LANGUAGE_INDEX = 0, + USER_INTERFACE_THEME_INDEX, + USER_INTERFACE_COUNT +}; + +enum +{ + IDENTITY_SERVER_INDEX, + IDENTITY_SERVER_DESCRIPTION_INDEX, + IDENTITY_SERVER_COUNT +}; + +enum +{ + OTHER_VERSION_INDEX = 0, + OTHER_OLM_VERSION_INDEX, + OTHER_COPYRIGHT_INDEX, + OTHER_TERM_CONDITIONS_INDEX, + OTHER_PRIVACY_INDEX, + OTHER_THIRD_PARTY_INDEX, + OTHER_CRASH_REPORT_INDEX, + OTHER_ENABLE_RAGESHAKE_INDEX, + OTHER_MARK_ALL_AS_READ_INDEX, + OTHER_CLEAR_CACHE_INDEX, + OTHER_REPORT_BUG_INDEX, + OTHER_COUNT +}; + +enum +{ + LABS_USE_ROOM_MEMBERS_LAZY_LOADING_INDEX = 0, + LABS_USE_JITSI_WIDGET_INDEX, + LABS_CROSS_SIGNING_INDEX, + LABS_COUNT +}; + +enum { + CRYPTOGRAPHY_INFO_INDEX = 0, + CRYPTOGRAPHY_BLACKLIST_UNVERIFIED_DEVICES_INDEX, + CRYPTOGRAPHY_EXPORT_INDEX, + CRYPTOGRAPHY_COUNT +}; + +enum +{ + DEVICES_DESCRIPTION_INDEX = 0 +}; + +#define SECTION_TITLE_PADDING_WHEN_HIDDEN 0.01f + +typedef void (^blockSettingsViewController_onReadyToDestroy)(void); + + +@interface SecurityViewController () +{ + // Current alert (if any). + UIAlertController *currentAlert; + + // listener + id removedAccountObserver; + id accountUserInfoObserver; + id pushInfoUpdateObserver; + + id notificationCenterWillUpdateObserver; + id notificationCenterDidUpdateObserver; + id notificationCenterDidFailObserver; + + // profile updates + // avatar + UIImage* newAvatarImage; + // the avatar image has been uploaded + NSString* uploadedAvatarURL; + + // new display name + NSString* newDisplayName; + + // password update + UITextField* currentPasswordTextField; + UITextField* newPasswordTextField1; + UITextField* newPasswordTextField2; + UIAlertAction* savePasswordAction; + + // New email address to bind + UITextField* newEmailTextField; + + // New phone number to bind + TableViewCellWithPhoneNumberTextField * newPhoneNumberCell; + CountryPickerViewController *newPhoneNumberCountryPicker; + NBPhoneNumber *newPhoneNumber; + + // Dynamic rows in the user settings section + NSInteger userSettingsProfilePictureIndex; + NSInteger userSettingsDisplayNameIndex; + NSInteger userSettingsFirstNameIndex; + NSInteger userSettingsSurnameIndex; + NSInteger userSettingsEmailStartIndex; // The user can have several linked emails. Hence, the dynamic section items count + NSInteger userSettingsNewEmailIndex; // This index also marks the end of the emails list + NSInteger userSettingsPhoneStartIndex; // The user can have several linked phone numbers. Hence, the dynamic section items count + NSInteger userSettingsNewPhoneIndex; // This index also marks the end of the phone numbers list + NSInteger userSettingsChangePasswordIndex; + NSInteger userSettingsThreePidsInformation; + NSInteger userSettingsNightModeSepIndex; + NSInteger userSettingsNightModeIndex; + + // Dynamic rows in the local contacts section + NSInteger localContactsSyncIndex; + NSInteger localContactsPhoneBookCountryIndex; + + // Devices + NSMutableArray *devicesArray; + DeviceView *deviceView; + + // Flair: the groups data source + GroupsDataSource *groupsDataSource; + + // Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar. + id kAppDelegateDidTapStatusBarNotificationObserver; + + // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. + id kThemeServiceDidChangeThemeNotificationObserver; + + // Postpone destroy operation when saving, pwd reset or email binding is in progress + BOOL isSavingInProgress; + BOOL isResetPwdInProgress; + BOOL is3PIDBindingInProgress; + blockSettingsViewController_onReadyToDestroy onReadyToDestroyHandler; + + // + UIAlertController *resetPwdAlertController; + + // The view used to export e2e keys + MXKEncryptionKeysExportView *exportView; + + // The document interaction Controller used to export e2e keys + UIDocumentInteractionController *documentInteractionController; + NSURL *keyExportsFile; + NSTimer *keyExportsFileDeletionTimer; + + BOOL keepNewEmailEditing; + BOOL keepNewPhoneNumberEditing; + + // The current pushed view controller + UIViewController *pushedViewController; + + SettingsKeyBackupTableViewSection *keyBackupSection; + KeyBackupSetupCoordinatorBridgePresenter *keyBackupSetupCoordinatorBridgePresenter; + KeyBackupRecoverCoordinatorBridgePresenter *keyBackupRecoverCoordinatorBridgePresenter; + + SettingsIdentityServerCoordinatorBridgePresenter *identityServerSettingsCoordinatorBridgePresenter; +} + +/** + Flag indicating whether the user is typing an email to bind. + */ +@property (nonatomic) BOOL newEmailEditingEnabled; + +/** + Flag indicating whether the user is typing a phone number to bind. + */ +@property (nonatomic) BOOL newPhoneEditingEnabled; + +@property (nonatomic, weak) DeactivateAccountViewController *deactivateAccountViewController; +@property (nonatomic, strong) SignOutAlertPresenter *signOutAlertPresenter; +@property (nonatomic, weak) UIButton *signOutButton; +@property (nonatomic, strong) SingleImagePickerPresenter *imagePickerPresenter; + +@property (nonatomic, strong) SettingsDiscoveryViewModel *settingsDiscoveryViewModel; +@property (nonatomic, strong) SettingsDiscoveryTableViewSection *settingsDiscoveryTableViewSection; +@property (nonatomic, strong) SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter *discoveryThreePidDetailsPresenter; + +@end + +@implementation SecurityViewController + +#pragma mark - Setup & Teardown + ++ (SecurityViewController*)instantiateWithMatrixSession:(MXSession*)matrixSession +{ + SecurityViewController* viewController = [[UIStoryboard storyboardWithName:@"Security" bundle:[NSBundle mainBundle]] instantiateInitialViewController]; + [viewController addMatrixSession:matrixSession]; + return viewController; +} + +//- (void)destroy +//{ +//// id notificationObserver = self.themeDidChangeNotificationObserver; +//// +//// if (notificationObserver) +//// { +//// [[NSNotificationCenter defaultCenter] removeObserver:notificationObserver]; +//// } +// +// [super destroy]; +//} + + +#pragma mark - View life cycle + +- (void)finalizeInit +{ + [super finalizeInit]; + + // Setup `MXKViewControllerHandling` properties + self.enableBarTintColorStatusChange = NO; + self.rageShakeManager = [RageShakeManager sharedManager]; + + isSavingInProgress = NO; + isResetPwdInProgress = NO; + is3PIDBindingInProgress = NO; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + // Do any additional setup after loading the view, typically from a nib. + + self.navigationItem.title = NSLocalizedStringFromTable(@"settings_title", @"Vector", nil); + + // Remove back bar button title when pushing a view controller + self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; + + [self.tableView registerClass:MXKTableViewCellWithLabelAndTextField.class forCellReuseIdentifier:[MXKTableViewCellWithLabelAndTextField defaultReuseIdentifier]]; + [self.tableView registerClass:MXKTableViewCellWithLabelAndSwitch.class forCellReuseIdentifier:[MXKTableViewCellWithLabelAndSwitch defaultReuseIdentifier]]; + [self.tableView registerClass:MXKTableViewCellWithLabelAndMXKImageView.class forCellReuseIdentifier:[MXKTableViewCellWithLabelAndMXKImageView defaultReuseIdentifier]]; + [self.tableView registerClass:TableViewCellWithPhoneNumberTextField.class forCellReuseIdentifier:[TableViewCellWithPhoneNumberTextField defaultReuseIdentifier]]; + [self.tableView registerClass:GroupTableViewCellWithSwitch.class forCellReuseIdentifier:[GroupTableViewCellWithSwitch defaultReuseIdentifier]]; + [self.tableView registerNib:MXKTableViewCellWithTextView.nib forCellReuseIdentifier:[MXKTableViewCellWithTextView defaultReuseIdentifier]]; + + // Enable self sizing cells + self.tableView.rowHeight = UITableViewAutomaticDimension; + self.tableView.estimatedRowHeight = 50; + + // Add observer to handle removed accounts + removedAccountObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKAccountManagerDidRemoveAccountNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + + if ([MXKAccountManager sharedManager].accounts.count) + { + // Refresh table to remove this account + [self refreshSettings]; + } + + }]; + + // Add observer to handle accounts update + accountUserInfoObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKAccountUserInfoDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + + [self stopActivityIndicator]; + + [self refreshSettings]; + + }]; + + // Add observer to push settings + pushInfoUpdateObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKAccountPushKitActivityDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + + [self stopActivityIndicator]; + + [self refreshSettings]; + + }]; + + [self registerAccountDataDidChangeIdentityServerNotification]; + + // Add each matrix session, to update the view controller appearance according to mx sessions state + NSArray *sessions = [AppDelegate theDelegate].mxSessions; + for (MXSession *mxSession in sessions) + { + [self addMatrixSession:mxSession]; + } + + if (self.mainSession.crypto.backup) + { + MXDeviceInfo *deviceInfo = [self.mainSession.crypto.deviceList storedDevice:self.mainSession.matrixRestClient.credentials.userId + deviceId:self.mainSession.matrixRestClient.credentials.deviceId]; + + if (deviceInfo) + { + keyBackupSection = [[SettingsKeyBackupTableViewSection alloc] initWithKeyBackup:self.mainSession.crypto.backup userDevice:deviceInfo]; + keyBackupSection.delegate = self; + } + } + + [self setupDiscoverySection]; + + groupsDataSource = [[GroupsDataSource alloc] initWithMatrixSession:self.mainSession]; + [groupsDataSource finalizeInitialization]; + groupsDataSource.delegate = self; + + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:@selector(onSave:)]; + self.navigationItem.rightBarButtonItem.accessibilityIdentifier=@"SettingsVCNavBarSaveButton"; + + + // Observe user interface theme change. + kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + + [self userInterfaceThemeDidChange]; + + }]; + [self userInterfaceThemeDidChange]; + + self.signOutAlertPresenter = [SignOutAlertPresenter new]; + self.signOutAlertPresenter.delegate = self; +} + +- (void)userInterfaceThemeDidChange +{ + [ThemeService.shared.theme applyStyleOnNavigationBar:self.navigationController.navigationBar]; + + self.activityIndicator.backgroundColor = ThemeService.shared.theme.overlayBackgroundColor; + + // Check the table view style to select its bg color. + self.tableView.backgroundColor = ((self.tableView.style == UITableViewStylePlain) ? ThemeService.shared.theme.backgroundColor : ThemeService.shared.theme.headerBackgroundColor); + self.view.backgroundColor = self.tableView.backgroundColor; + self.tableView.separatorColor = ThemeService.shared.theme.lineBreakColor; + + if (self.tableView.dataSource) + { + [self refreshSettings]; + } +} + +- (UIStatusBarStyle)preferredStatusBarStyle +{ + return ThemeService.shared.theme.statusBarStyle; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +- (void)destroy +{ + if (groupsDataSource) + { + groupsDataSource.delegate = nil; + [groupsDataSource destroy]; + groupsDataSource = nil; + } + + // Release the potential pushed view controller + [self releasePushedViewController]; + + if (documentInteractionController) + { + [documentInteractionController dismissPreviewAnimated:NO]; + [documentInteractionController dismissMenuAnimated:NO]; + documentInteractionController = nil; + } + + if (kThemeServiceDidChangeThemeNotificationObserver) + { + [[NSNotificationCenter defaultCenter] removeObserver:kThemeServiceDidChangeThemeNotificationObserver]; + kThemeServiceDidChangeThemeNotificationObserver = nil; + } + + if (isSavingInProgress || isResetPwdInProgress || is3PIDBindingInProgress) + { + __weak typeof(self) weakSelf = self; + onReadyToDestroyHandler = ^() { + + if (weakSelf) + { + typeof(self) self = weakSelf; + [self destroy]; + } + + }; + } + else + { + // Dispose all resources + [self reset]; + + [super destroy]; + } + + keyBackupSetupCoordinatorBridgePresenter = nil; + keyBackupRecoverCoordinatorBridgePresenter = nil; + identityServerSettingsCoordinatorBridgePresenter = nil; +} + +- (void)onMatrixSessionStateDidChange:(NSNotification *)notif +{ + MXSession *mxSession = notif.object; + + // Check whether the concerned session is a new one which is not already associated with this view controller. + if (mxSession.state == MXSessionStateInitialised && [self.mxSessions indexOfObject:mxSession] != NSNotFound) + { + // Store this new session + [self addMatrixSession:mxSession]; + } + else + { + [super onMatrixSessionStateDidChange:notif]; + } +} + +- (void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + + // Screen tracking + [[Analytics sharedInstance] trackScreen:@"Settings"]; + + // Release the potential pushed view controller + [self releasePushedViewController]; + + // Refresh display + [self refreshSettings]; + + // Refresh linked emails and phone numbers in parallel + [self loadAccount3PIDs]; + + // Refresh the current device information in parallel + [self loadCurrentDeviceInformation]; + + // Refresh devices in parallel + [self loadDevices]; + + // Observe kAppDelegateDidTapStatusBarNotificationObserver. + kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + + [self.tableView setContentOffset:CGPointMake(-self.tableView.mxk_adjustedContentInset.left, -self.tableView.mxk_adjustedContentInset.top) animated:YES]; + + }]; + + newPhoneNumberCountryPicker = nil; +} + +- (void)viewDidAppear:(BOOL)animated +{ + [super viewDidAppear:animated]; + + [self.settingsDiscoveryTableViewSection reload]; +} + +- (void)viewWillDisappear:(BOOL)animated +{ + [super viewWillDisappear:animated]; + + if (currentAlert) + { + [currentAlert dismissViewControllerAnimated:NO completion:nil]; + currentAlert = nil; + } + + if (resetPwdAlertController) + { + [resetPwdAlertController dismissViewControllerAnimated:NO completion:nil]; + resetPwdAlertController = nil; + } + + if (notificationCenterWillUpdateObserver) + { + [[NSNotificationCenter defaultCenter] removeObserver:notificationCenterWillUpdateObserver]; + notificationCenterWillUpdateObserver = nil; + } + + if (notificationCenterDidUpdateObserver) + { + [[NSNotificationCenter defaultCenter] removeObserver:notificationCenterDidUpdateObserver]; + notificationCenterDidUpdateObserver = nil; + } + + if (notificationCenterDidFailObserver) + { + [[NSNotificationCenter defaultCenter] removeObserver:notificationCenterDidFailObserver]; + notificationCenterDidFailObserver = nil; + } + + if (kAppDelegateDidTapStatusBarNotificationObserver) + { + [[NSNotificationCenter defaultCenter] removeObserver:kAppDelegateDidTapStatusBarNotificationObserver]; + kAppDelegateDidTapStatusBarNotificationObserver = nil; + } +} + +#pragma mark - Internal methods + +- (void)pushViewController:(UIViewController*)viewController +{ + // Keep ref on pushed view controller + pushedViewController = viewController; + + // Hide back button title + self.navigationItem.backBarButtonItem =[[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; + + [self.navigationController pushViewController:viewController animated:YES]; +} + +- (void)releasePushedViewController +{ + if (pushedViewController) + { + if ([pushedViewController isKindOfClass:[UINavigationController class]]) + { + UINavigationController *navigationController = (UINavigationController*)pushedViewController; + for (id subViewController in navigationController.viewControllers) + { + if ([subViewController respondsToSelector:@selector(destroy)]) + { + [subViewController destroy]; + } + } + } + else if ([pushedViewController respondsToSelector:@selector(destroy)]) + { + [(id)pushedViewController destroy]; + } + + pushedViewController = nil; + } +} + +- (void)dismissKeyboard +{ + [currentPasswordTextField resignFirstResponder]; + [newPasswordTextField1 resignFirstResponder]; + [newPasswordTextField2 resignFirstResponder]; + [newEmailTextField resignFirstResponder]; + [newPhoneNumberCell.mxkTextField resignFirstResponder]; +} + +- (void)reset +{ + // Remove observers + if (removedAccountObserver) + { + [[NSNotificationCenter defaultCenter] removeObserver:removedAccountObserver]; + removedAccountObserver = nil; + } + + if (accountUserInfoObserver) + { + [[NSNotificationCenter defaultCenter] removeObserver:accountUserInfoObserver]; + accountUserInfoObserver = nil; + } + + if (pushInfoUpdateObserver) + { + [[NSNotificationCenter defaultCenter] removeObserver:pushInfoUpdateObserver]; + pushInfoUpdateObserver = nil; + } + + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + onReadyToDestroyHandler = nil; + + if (deviceView) + { + [deviceView removeFromSuperview]; + deviceView = nil; + } +} + +-(void)setNewEmailEditingEnabled:(BOOL)newEmailEditingEnabled +{ + if (newEmailEditingEnabled != _newEmailEditingEnabled) + { + // Update the flag + _newEmailEditingEnabled = newEmailEditingEnabled; + + if (!newEmailEditingEnabled) + { + // Dismiss the keyboard + [newEmailTextField resignFirstResponder]; + newEmailTextField = nil; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + + [self.tableView beginUpdates]; + + // Refresh the corresponding table view cell with animation + [self.tableView reloadRowsAtIndexPaths:@[ + [NSIndexPath indexPathForRow:userSettingsNewEmailIndex inSection:SETTINGS_SECTION_USER_SETTINGS_INDEX] + ] + withRowAnimation:UITableViewRowAnimationFade]; + + [self.tableView endUpdates]; + }); + } +} + +-(void)setNewPhoneEditingEnabled:(BOOL)newPhoneEditingEnabled +{ + if (newPhoneEditingEnabled != _newPhoneEditingEnabled) + { + // Update the flag + _newPhoneEditingEnabled = newPhoneEditingEnabled; + + if (!newPhoneEditingEnabled) + { + // Dismiss the keyboard + [newPhoneNumberCell.mxkTextField resignFirstResponder]; + newPhoneNumberCell = nil; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + + [self.tableView beginUpdates]; + + // Refresh the corresponding table view cell with animation + [self.tableView reloadRowsAtIndexPaths:@[ + [NSIndexPath indexPathForRow:userSettingsNewPhoneIndex inSection:SETTINGS_SECTION_USER_SETTINGS_INDEX] + ] + withRowAnimation:UITableViewRowAnimationFade]; + + [self.tableView endUpdates]; + }); + } +} + +- (void)showValidationEmailDialogWithMessage:(NSString*)message for3PidAddSession:(MX3PidAddSession*)threePidAddSession threePidAddManager:(MX3PidAddManager*)threePidAddManager password:(NSString*)password +{ + MXWeakify(self); + [currentAlert dismissViewControllerAnimated:NO completion:nil]; + currentAlert = [UIAlertController alertControllerWithTitle:[NSBundle mxk_localizedStringForKey:@"account_email_validation_title"] message:message preferredStyle:UIAlertControllerStyleAlert]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + self->currentAlert = nil; + [self stopActivityIndicator]; + + // Reset new email adding + self.newEmailEditingEnabled = NO; + }]]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"continue"] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + [self tryFinaliseAddEmailSession:threePidAddSession withPassword:password threePidAddManager:threePidAddManager]; + }]]; + + [currentAlert mxk_setAccessibilityIdentifier:@"SettingsVCEmailValidationAlert"]; + [self presentViewController:currentAlert animated:YES completion:nil]; +} + +- (void)tryFinaliseAddEmailSession:(MX3PidAddSession*)threePidAddSession withPassword:(NSString*)password threePidAddManager:(MX3PidAddManager*)threePidAddManager +{ + self->is3PIDBindingInProgress = YES; + + [threePidAddManager tryFinaliseAddEmailSession:threePidAddSession withPassword:password success:^{ + + self->is3PIDBindingInProgress = NO; + + // Check whether destroy has been called during email binding + if (self->onReadyToDestroyHandler) + { + // Ready to destroy + self->onReadyToDestroyHandler(); + self->onReadyToDestroyHandler = nil; + } + else + { + self->currentAlert = nil; + + [self stopActivityIndicator]; + + // Reset new email adding + self.newEmailEditingEnabled = NO; + + // Update linked emails + [self loadAccount3PIDs]; + } + + } failure:^(NSError * _Nonnull error) { + NSLog(@"[SettingsViewController] tryFinaliseAddEmailSession: Failed to bind email"); + + MXError *mxError = [[MXError alloc] initWithNSError:error]; + if (mxError && [mxError.errcode isEqualToString:kMXErrCodeStringForbidden]) + { + NSLog(@"[SettingsViewController] tryFinaliseAddEmailSession: Wrong password"); + + // Ask password again + self->currentAlert = [UIAlertController alertControllerWithTitle:nil + message:NSLocalizedStringFromTable(@"settings_add_3pid_invalid_password_message", @"Vector", nil) + preferredStyle:UIAlertControllerStyleAlert]; + + [self->currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"retry", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + self->currentAlert = nil; + + [self requestAccountPasswordWithTitle:NSLocalizedStringFromTable(@"settings_add_3pid_password_title_email", @"Vector", nil) + message:NSLocalizedStringFromTable(@"settings_add_3pid_password_message", @"Vector", nil) + onComplete:^(NSString *password) { + [self tryFinaliseAddEmailSession:threePidAddSession withPassword:password threePidAddManager:threePidAddManager]; + }]; + }]]; + + [self presentViewController:self->currentAlert animated:YES completion:nil]; + + return; + } + + self->is3PIDBindingInProgress = NO; + + // Check whether destroy has been called during email binding + if (self->onReadyToDestroyHandler) + { + // Ready to destroy + self->onReadyToDestroyHandler(); + self->onReadyToDestroyHandler = nil; + } + else + { + self->currentAlert = nil; + + // Display the same popup again if the error is M_THREEPID_AUTH_FAILED + MXError *mxError = [[MXError alloc] initWithNSError:error]; + if (mxError && [mxError.errcode isEqualToString:kMXErrCodeStringThreePIDAuthFailed]) + { + [self showValidationEmailDialogWithMessage:[NSBundle mxk_localizedStringForKey:@"account_email_validation_error"] + for3PidAddSession:threePidAddSession + threePidAddManager:threePidAddManager + password:password]; + } + else + { + [self stopActivityIndicator]; + + // Notify user + NSString *myUserId = self.mainSession.myUser.userId; // TODO: Hanlde multi-account + [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; + } + } + }]; +} + +- (void)showValidationMsisdnDialogWithMessage:(NSString*)message for3PidAddSession:(MX3PidAddSession*)threePidAddSession threePidAddManager:(MX3PidAddManager*)threePidAddManager password:(NSString*)password +{ + MXWeakify(self); + + [currentAlert dismissViewControllerAnimated:NO completion:nil]; + currentAlert = [UIAlertController alertControllerWithTitle:[NSBundle mxk_localizedStringForKey:@"account_msisdn_validation_title"] message:message preferredStyle:UIAlertControllerStyleAlert]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + + self->currentAlert = nil; + + [self stopActivityIndicator]; + + // Reset new phone adding + self.newPhoneEditingEnabled = NO; + }]]; + + [currentAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.secureTextEntry = NO; + textField.placeholder = nil; + textField.keyboardType = UIKeyboardTypeDecimalPad; + }]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"submit"] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + + MXStrongifyAndReturnIfNil(self); + + NSString *smsCode = [self->currentAlert textFields].firstObject.text; + + self->currentAlert = nil; + + if (smsCode.length) + { + [self finaliseAddPhoneNumberSession:threePidAddSession withToken:smsCode andPassword:password message:message threePidAddManager:threePidAddManager]; + } + else + { + // Ask again the sms token + [self showValidationMsisdnDialogWithMessage:message for3PidAddSession:threePidAddSession threePidAddManager:threePidAddManager password:password]; + } + }]]; + + [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCMsisdnValidationAlert"]; + [self presentViewController:currentAlert animated:YES completion:nil]; +} + +- (void)finaliseAddPhoneNumberSession:(MX3PidAddSession*)threePidAddSession withToken:(NSString*)token andPassword:(NSString*)password message:(NSString*)message threePidAddManager:(MX3PidAddManager*)threePidAddManager +{ + self->is3PIDBindingInProgress = YES; + + [threePidAddManager finaliseAddPhoneNumberSession:threePidAddSession withToken:token password:password success:^{ + + self->is3PIDBindingInProgress = NO; + + // Check whether destroy has been called during the binding + if (self->onReadyToDestroyHandler) + { + // Ready to destroy + self->onReadyToDestroyHandler(); + self->onReadyToDestroyHandler = nil; + } + else + { + [self stopActivityIndicator]; + + // Reset new phone adding + self.newPhoneEditingEnabled = NO; + + // Update linked 3pids + [self loadAccount3PIDs]; + } + + } failure:^(NSError * _Nonnull error) { + + NSLog(@"[SettingsViewController] finaliseAddPhoneNumberSession: Failed to submit the sms token"); + + MXError *mxError = [[MXError alloc] initWithNSError:error]; + if (mxError && [mxError.errcode isEqualToString:kMXErrCodeStringForbidden]) + { + NSLog(@"[SettingsViewController] finaliseAddPhoneNumberSession: Wrong password"); + + // Ask password again + self->currentAlert = [UIAlertController alertControllerWithTitle:nil + message:NSLocalizedStringFromTable(@"settings_add_3pid_invalid_password_message", @"Vector", nil) + preferredStyle:UIAlertControllerStyleAlert]; + + [self->currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"retry", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + self->currentAlert = nil; + + [self requestAccountPasswordWithTitle:NSLocalizedStringFromTable(@"settings_add_3pid_password_title_msidsn", @"Vector", nil) + message:NSLocalizedStringFromTable(@"settings_add_3pid_password_message", @"Vector", nil) + onComplete:^(NSString *password) { + [self finaliseAddPhoneNumberSession:threePidAddSession withToken:token andPassword:password message:message threePidAddManager:threePidAddManager]; + }]; + }]]; + + [self presentViewController:self->currentAlert animated:YES completion:nil]; + + return; + } + + self->is3PIDBindingInProgress = NO; + + // Check whether destroy has been called during phone binding + if (self->onReadyToDestroyHandler) + { + // Ready to destroy + self->onReadyToDestroyHandler(); + self->onReadyToDestroyHandler = nil; + } + else + { + // Ignore connection cancellation error + if (([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled)) + { + [self stopActivityIndicator]; + return; + } + + // Alert user + NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey]; + NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey]; + if (!title) + { + if (msg) + { + title = msg; + msg = nil; + } + else + { + title = [NSBundle mxk_localizedStringForKey:@"error"]; + } + } + + + self->currentAlert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert]; + + [self->currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + self->currentAlert = nil; + + // Ask again the sms token + [self showValidationMsisdnDialogWithMessage:message for3PidAddSession:threePidAddSession threePidAddManager:threePidAddManager password:password]; + }]]; + + [self->currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCErrorAlert"]; + [self presentViewController:self->currentAlert animated:YES completion:nil]; + } + }]; +} + +- (void)loadAccount3PIDs +{ + // Refresh the account 3PIDs list + MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; + [account load3PIDs:^{ + + NSArray *thirdPartyIdentifiers = account.threePIDs ?: @[]; + [self.settingsDiscoveryViewModel updateWithThirdPartyIdentifiers:thirdPartyIdentifiers]; + + // Refresh all the table (A slide down animation is observed when we limit the refresh to the concerned section). + // Note: The use of 'reloadData' handles the case where the account has been logged out. + [self refreshSettings]; + + } failure:^(NSError *error) { + + // Display the data that has been loaded last time + // Note: The use of 'reloadData' handles the case where the account has been logged out. + [self refreshSettings]; + + }]; +} + +- (void)loadCurrentDeviceInformation +{ + // Refresh the current device information + MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; + [account loadDeviceInformation:^{ + + // Refresh all the table (A slide down animation is observed when we limit the refresh to the concerned section). + // Note: The use of 'reloadData' handles the case where the account has been logged out. + [self refreshSettings]; + + } failure:nil]; +} + +- (NSAttributedString*)cryptographyInformation +{ + // TODO Handle multi accounts + MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; + + // Crypto information + NSMutableAttributedString *cryptoInformationString = [[NSMutableAttributedString alloc] + initWithString:NSLocalizedStringFromTable(@"settings_crypto_device_name", @"Vector", nil) + attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor, + NSFontAttributeName: [UIFont systemFontOfSize:17]}]; + [cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc] + initWithString:account.device.displayName ? account.device.displayName : @"" + attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor, + NSFontAttributeName: [UIFont systemFontOfSize:17]}]]; + + [cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc] + initWithString:NSLocalizedStringFromTable(@"settings_crypto_device_id", @"Vector", nil) + attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor, + NSFontAttributeName: [UIFont systemFontOfSize:17]}]]; + [cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc] + initWithString:account.device.deviceId ? account.device.deviceId : @"" + attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor, + NSFontAttributeName: [UIFont systemFontOfSize:17]}]]; + + [cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc] + initWithString:NSLocalizedStringFromTable(@"settings_crypto_device_key", @"Vector", nil) + attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor, + NSFontAttributeName: [UIFont systemFontOfSize:17]}]]; + NSString *fingerprint = account.mxSession.crypto.deviceEd25519Key; + if (fingerprint) + { + fingerprint = [MXTools addWhiteSpacesToString:fingerprint every:4]; + } + [cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc] + initWithString:fingerprint ? fingerprint : @"" + attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor, + NSFontAttributeName: [UIFont boldSystemFontOfSize:17]}]]; + + return cryptoInformationString; +} + +- (void)loadDevices +{ + // Refresh the account devices list + MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; + [account.mxRestClient devices:^(NSArray *devices) { + + if (devices) + { + devicesArray = [NSMutableArray arrayWithArray:devices]; + + // Sort devices according to the last seen date. + NSComparator comparator = ^NSComparisonResult(MXDevice *deviceA, MXDevice *deviceB) { + + if (deviceA.lastSeenTs > deviceB.lastSeenTs) + { + return NSOrderedAscending; + } + if (deviceA.lastSeenTs < deviceB.lastSeenTs) + { + return NSOrderedDescending; + } + + return NSOrderedSame; + }; + + // Sort devices list + [devicesArray sortUsingComparator:comparator]; + } + else + { + devicesArray = nil; + + } + + // Refresh all the table (A slide down animation is observed when we limit the refresh to the concerned section). + // Note: The use of 'reloadData' handles the case where the account has been logged out. + [self refreshSettings]; + + } failure:^(NSError *error) { + + // Display the data that has been loaded last time + // Note: The use of 'reloadData' handles the case where the account has been logged out. + [self refreshSettings]; + + }]; +} + +- (void)showDeviceDetails:(MXDevice *)device +{ + [self dismissKeyboard]; + + deviceView = [[DeviceView alloc] initWithDevice:device andMatrixSession:self.mainSession]; + deviceView.delegate = self; + + // Add the view and define edge constraints + [self.tableView.superview addSubview:deviceView]; + [self.tableView.superview bringSubviewToFront:deviceView]; + + NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:deviceView + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:self.tableView + attribute:NSLayoutAttributeTop + multiplier:1.0f + constant:0.0f]; + + NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:deviceView + attribute:NSLayoutAttributeLeft + relatedBy:NSLayoutRelationEqual + toItem:self.tableView + attribute:NSLayoutAttributeLeft + multiplier:1.0f + constant:0.0f]; + + NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:deviceView + attribute:NSLayoutAttributeWidth + relatedBy:NSLayoutRelationEqual + toItem:self.tableView + attribute:NSLayoutAttributeWidth + multiplier:1.0f + constant:0.0f]; + + NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:deviceView + attribute:NSLayoutAttributeHeight + relatedBy:NSLayoutRelationEqual + toItem:self.tableView + attribute:NSLayoutAttributeHeight + multiplier:1.0f + constant:0.0f]; + + [NSLayoutConstraint activateConstraints:@[topConstraint, leftConstraint, widthConstraint, heightConstraint]]; +} + +- (void)deviceView:(DeviceView*)theDeviceView presentAlertController:(UIAlertController *)alert +{ + [self dismissKeyboard]; + + [self presentViewController:alert animated:YES completion:nil]; +} + +- (void)dismissDeviceView:(MXKDeviceView *)theDeviceView didUpdate:(BOOL)isUpdated +{ + [deviceView removeFromSuperview]; + deviceView = nil; + + if (isUpdated) + { + [self loadDevices]; + } +} + +- (void)editNewEmailTextField +{ + if (newEmailTextField && ![newEmailTextField becomeFirstResponder]) + { + // Retry asynchronously + dispatch_async(dispatch_get_main_queue(), ^{ + + [self editNewEmailTextField]; + + }); + } +} + +- (void)editNewPhoneNumberTextField +{ + if (newPhoneNumberCell && ![newPhoneNumberCell.mxkTextField becomeFirstResponder]) + { + // Retry asynchronously + dispatch_async(dispatch_get_main_queue(), ^{ + + [self editNewPhoneNumberTextField]; + + }); + } +} + +- (void)refreshSettings +{ + // Check whether a text input is currently edited + keepNewEmailEditing = newEmailTextField ? newEmailTextField.isFirstResponder : NO; + keepNewPhoneNumberEditing = newPhoneNumberCell ? newPhoneNumberCell.mxkTextField.isFirstResponder : NO; + + // Trigger a full table reloadData + [self.tableView reloadData]; + + // Restore the previous edited field + if (keepNewEmailEditing) + { + [self editNewEmailTextField]; + keepNewEmailEditing = NO; + } + else if (keepNewPhoneNumberEditing) + { + [self editNewPhoneNumberTextField]; + keepNewPhoneNumberEditing = NO; + } +} + +- (void)formatNewPhoneNumber +{ + if (newPhoneNumber) + { + NSString *formattedNumber = [[NBPhoneNumberUtil sharedInstance] format:newPhoneNumber numberFormat:NBEPhoneNumberFormatINTERNATIONAL error:nil]; + NSString *prefix = newPhoneNumberCell.mxkLabel.text; + if ([formattedNumber hasPrefix:prefix]) + { + // Format the display phone number + newPhoneNumberCell.mxkTextField.text = [formattedNumber substringFromIndex:prefix.length]; + } + } +} + +- (void)setupDiscoverySection +{ + MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; + + NSArray *thirdPartyIdentifiers = account.threePIDs ?: @[]; + + SettingsDiscoveryViewModel *viewModel = [[SettingsDiscoveryViewModel alloc] initWithSession:self.mainSession thirdPartyIdentifiers:thirdPartyIdentifiers]; + viewModel.coordinatorDelegate = self; + + SettingsDiscoveryTableViewSection *discoverySection = [[SettingsDiscoveryTableViewSection alloc] initWithViewModel:viewModel]; + discoverySection.delegate = self; + + self.settingsDiscoveryViewModel = viewModel; + self.settingsDiscoveryTableViewSection = discoverySection; +} + +#pragma mark - 3Pid Add + +-(void)checkAuthenticationFlowForAdding:(MX3PIDMedium)medium withSession:(MXSession*)session onComplete:(void (^)(NSString *password))onComplete +{ + [self startActivityIndicator]; + + [session.threePidAddManager authenticationFlowForAdd3PidWithSuccess:^(NSArray * _Nullable flows) { + [self stopActivityIndicator]; + + if (flows) + { + // We support only "m.login.password" + BOOL hasPasswordFlow = NO; + for (MXLoginFlow *flow in flows) + { + if ([flow.stages containsObject:kMXLoginFlowTypePassword]) + { + hasPasswordFlow = YES; + break; + } + } + + if (hasPasswordFlow) + { + // Ask password to the user while we are here + NSString *title = NSLocalizedStringFromTable(@"settings_add_3pid_password_title_email", @"Vector", nil); + if ([medium isEqualToString:kMX3PIDMediumMSISDN]) + { + title = NSLocalizedStringFromTable(@"settings_add_3pid_password_title_msidsn", @"Vector", nil); + } + + [self requestAccountPasswordWithTitle:title + message:NSLocalizedStringFromTable(@"settings_add_3pid_password_message", @"Vector", nil) + onComplete:onComplete]; + } + else + { + // The user needs to use Riot-web + NSString *appName = [[NSBundle mainBundle] infoDictionary][@"CFBundleDisplayName"]; + NSString *message = [NSString stringWithFormat:NSLocalizedStringFromTable(@"error_not_supported_on_mobile", @"Vector", nil), appName]; + [[AppDelegate theDelegate] showAlertWithTitle:nil message:message]; + } + } + else + { + // No auth + onComplete(nil); + } + + } failure:^(NSError * _Nonnull error) { + [self stopActivityIndicator]; + [[AppDelegate theDelegate] showErrorAsAlert:error]; + }]; +} + +- (void)requestAccountPasswordWithTitle:(NSString*)title message:(NSString*)message onComplete:(void (^)(NSString *password))onComplete +{ + [currentAlert dismissViewControllerAnimated:NO completion:nil]; + + // Prompt the user before deleting the device. + currentAlert = [UIAlertController alertControllerWithTitle:title + message:message + preferredStyle:UIAlertControllerStyleAlert]; + + [currentAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.secureTextEntry = YES; + textField.placeholder = nil; + textField.keyboardType = UIKeyboardTypeDefault; + }]; + + MXWeakify(self); + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) + { + MXStrongifyAndReturnIfNil(self); + self->currentAlert = nil; + }]]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"continue"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + + UITextField *textField = [self->currentAlert textFields].firstObject; + self->currentAlert = nil; + + onComplete(textField.text); + }]]; + + [self presentViewController:currentAlert animated:YES completion:nil]; +} + +#pragma mark - Segues + +- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender +{ + // Keep ref on destinationViewController + [super prepareForSegue:segue sender:sender]; + + // FIXME add night mode +} + +#pragma mark - UITableView data source + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + // update the save button if there is an update + [self updateSaveButtonStatus]; + + return SETTINGS_SECTION_COUNT; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + NSInteger count = 0; + + if (section == SETTINGS_SECTION_SIGN_OUT_INDEX) + { + count = 1; + } + else if (section == SETTINGS_SECTION_USER_SETTINGS_INDEX) + { + MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; + + userSettingsProfilePictureIndex = 0; + userSettingsDisplayNameIndex = 1; + userSettingsChangePasswordIndex = 2; + + // Hide some unsupported account settings + userSettingsFirstNameIndex = -1; + userSettingsSurnameIndex = -1; + userSettingsNightModeSepIndex = -1; + userSettingsNightModeIndex = -1; + + userSettingsEmailStartIndex = 3; + userSettingsNewEmailIndex = userSettingsEmailStartIndex + account.linkedEmails.count; + userSettingsPhoneStartIndex = userSettingsNewEmailIndex + 1; + userSettingsNewPhoneIndex = userSettingsPhoneStartIndex + account.linkedPhoneNumbers.count; + userSettingsThreePidsInformation = userSettingsNewPhoneIndex + 1; + + count = userSettingsThreePidsInformation + 1; + } + else if (section == SETTINGS_SECTION_NOTIFICATIONS_SETTINGS_INDEX) + { + count = NOTIFICATION_SETTINGS_COUNT; + } + else if (section == SETTINGS_SECTION_CALLS_INDEX) + { + count = CALLS_COUNT; + + if (!RiotSettings.shared.stunServerFallback) + { + count -= 2; + } + } + else if (section == SETTINGS_SECTION_DISCOVERY_INDEX) + { + count = self.settingsDiscoveryTableViewSection.numberOfRows; + } + else if (section == SETTINGS_SECTION_IDENTITY_SERVER_INDEX) + { + count = IDENTITY_SERVER_COUNT; + } + else if (section == SETTINGS_SECTION_INTEGRATIONS_INDEX) + { + count = INTEGRATIONS_COUNT; + } + else if (section == SETTINGS_SECTION_USER_INTERFACE_INDEX) + { + count = USER_INTERFACE_COUNT; + } + else if (section == SETTINGS_SECTION_IGNORED_USERS_INDEX) + { + if ([AppDelegate theDelegate].mxSessions.count > 0) + { + MXSession* session = [AppDelegate theDelegate].mxSessions[0]; + count = session.ignoredUsers.count; + } + else + { + count = 0; + } + } + else if (section == SETTINGS_SECTION_CONTACTS_INDEX) + { + localContactsSyncIndex = count++; + + if ([MXKAppSettings standardAppSettings].syncLocalContacts) + { + localContactsPhoneBookCountryIndex = count++; + } + else + { + localContactsPhoneBookCountryIndex = -1; + } + } + else if (section == SETTINGS_SECTION_ADVANCED_INDEX) + { + count = 1; + } + else if (section == SETTINGS_SECTION_OTHER_INDEX) + { + count = OTHER_COUNT; + } + else if (section == SETTINGS_SECTION_LABS_INDEX) + { + count = LABS_COUNT; + } + else if (section == SETTINGS_SECTION_FLAIR_INDEX) + { + // Check whether some joined groups are available + if ([groupsDataSource numberOfSectionsInTableView:tableView]) + { + if (groupsDataSource.joinedGroupsSection != -1) + { + count = [groupsDataSource tableView:tableView numberOfRowsInSection:groupsDataSource.joinedGroupsSection]; + } + } + } + else if (section == SETTINGS_SECTION_DEVICES_INDEX) + { + count = devicesArray.count; + if (count) + { + // For some description (DEVICES_DESCRIPTION_INDEX) + count++; + } + } + else if (section == SETTINGS_SECTION_CRYPTOGRAPHY_INDEX) + { + // Check whether this section is visible. + if (self.mainSession.crypto) + { + count = CRYPTOGRAPHY_COUNT; + } + } + else if (section == SETTINGS_SECTION_KEYBACKUP_INDEX) + { + // Check whether this section is visible. + if (self.mainSession.crypto) + { + count = keyBackupSection.numberOfRows; + } + } + else if (section == SETTINGS_SECTION_DEACTIVATE_ACCOUNT_INDEX) + { + count = 1; + } + return count; +} + +- (MXKTableViewCellWithLabelAndTextField*)getLabelAndTextFieldCell:(UITableView*)tableview forIndexPath:(NSIndexPath *)indexPath +{ + MXKTableViewCellWithLabelAndTextField *cell = [tableview dequeueReusableCellWithIdentifier:[MXKTableViewCellWithLabelAndTextField defaultReuseIdentifier] forIndexPath:indexPath]; + + cell.mxkLabelLeadingConstraint.constant = cell.separatorInset.left; + cell.mxkTextFieldLeadingConstraint.constant = 16; + cell.mxkTextFieldTrailingConstraint.constant = 15; + + cell.mxkLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + + cell.mxkTextField.userInteractionEnabled = YES; + cell.mxkTextField.borderStyle = UITextBorderStyleNone; + cell.mxkTextField.textAlignment = NSTextAlignmentRight; + cell.mxkTextField.textColor = ThemeService.shared.theme.textSecondaryColor; + cell.mxkTextField.font = [UIFont systemFontOfSize:16]; + cell.mxkTextField.placeholder = nil; + + cell.accessoryType = UITableViewCellAccessoryNone; + cell.accessoryView = nil; + + cell.alpha = 1.0f; + cell.userInteractionEnabled = YES; + + [cell layoutIfNeeded]; + + return cell; +} + +- (MXKTableViewCellWithLabelAndSwitch*)getLabelAndSwitchCell:(UITableView*)tableview forIndexPath:(NSIndexPath *)indexPath +{ + MXKTableViewCellWithLabelAndSwitch *cell = [tableview dequeueReusableCellWithIdentifier:[MXKTableViewCellWithLabelAndSwitch defaultReuseIdentifier] forIndexPath:indexPath]; + + cell.mxkLabelLeadingConstraint.constant = cell.separatorInset.left; + cell.mxkSwitchTrailingConstraint.constant = 15; + + cell.mxkLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + + [cell.mxkSwitch removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; + + // Force layout before reusing a cell (fix switch displayed outside the screen) + [cell layoutIfNeeded]; + + return cell; +} + +- (MXKTableViewCell*)getDefaultTableViewCell:(UITableView*)tableView +{ + MXKTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCell defaultReuseIdentifier]]; + if (!cell) + { + cell = [[MXKTableViewCell alloc] init]; + } + else + { + cell.selectionStyle = UITableViewCellSelectionStyleDefault; + + cell.accessoryType = UITableViewCellAccessoryNone; + cell.accessoryView = nil; + } + cell.textLabel.accessibilityIdentifier = nil; + cell.textLabel.font = [UIFont systemFontOfSize:17]; + cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + cell.contentView.backgroundColor = UIColor.clearColor; + + return cell; +} + +- (MXKTableViewCellWithTextView*)textViewCellForTableView:(UITableView*)tableView atIndexPath:(NSIndexPath *)indexPath +{ + MXKTableViewCellWithTextView *textViewCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithTextView defaultReuseIdentifier] forIndexPath:indexPath]; + + textViewCell.mxkTextView.textColor = ThemeService.shared.theme.textPrimaryColor; + textViewCell.mxkTextView.font = [UIFont systemFontOfSize:17]; + textViewCell.mxkTextView.backgroundColor = [UIColor clearColor]; + textViewCell.mxkTextViewLeadingConstraint.constant = tableView.separatorInset.left; + textViewCell.mxkTextViewTrailingConstraint.constant = tableView.separatorInset.right; + textViewCell.mxkTextView.accessibilityIdentifier = nil; + + return textViewCell; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + NSInteger section = indexPath.section; + NSInteger row = indexPath.row; + + // set the cell to a default value to avoid application crashes + UITableViewCell *cell = [[UITableViewCell alloc] init]; + cell.backgroundColor = [UIColor redColor]; + + // check if there is a valid session + if (([AppDelegate theDelegate].mxSessions.count == 0) || ([MXKAccountManager sharedManager].activeAccounts.count == 0)) + { + // else use a default cell + return cell; + } + + MXSession* session = [AppDelegate theDelegate].mxSessions[0]; + MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; + + if (section == SETTINGS_SECTION_SIGN_OUT_INDEX) + { + MXKTableViewCellWithButton *signOutCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; + if (!signOutCell) + { + signOutCell = [[MXKTableViewCellWithButton alloc] init]; + } + else + { + // Fix https://github.com/vector-im/riot-ios/issues/1354 + // Do not move this line in prepareForReuse because of https://github.com/vector-im/riot-ios/issues/1323 + signOutCell.mxkButton.titleLabel.text = nil; + } + + NSString* title = NSLocalizedStringFromTable(@"settings_sign_out", @"Vector", nil); + + [signOutCell.mxkButton setTitle:title forState:UIControlStateNormal]; + [signOutCell.mxkButton setTitle:title forState:UIControlStateHighlighted]; + [signOutCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; + signOutCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; + + [signOutCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; + [signOutCell.mxkButton addTarget:self action:@selector(onSignout:) forControlEvents:UIControlEventTouchUpInside]; + signOutCell.mxkButton.accessibilityIdentifier=@"SettingsVCSignOutButton"; + + cell = signOutCell; + } + else if (section == SETTINGS_SECTION_USER_SETTINGS_INDEX) + { + MXMyUser* myUser = session.myUser; + + if (row == userSettingsProfilePictureIndex) + { + MXKTableViewCellWithLabelAndMXKImageView *profileCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithLabelAndMXKImageView defaultReuseIdentifier] forIndexPath:indexPath]; + + profileCell.mxkLabelLeadingConstraint.constant = profileCell.separatorInset.left; + profileCell.mxkImageViewTrailingConstraint.constant = 10; + + profileCell.mxkImageViewWidthConstraint.constant = profileCell.mxkImageViewHeightConstraint.constant = 30; + profileCell.mxkImageViewDisplayBoxType = MXKTableViewCellDisplayBoxTypeCircle; + + if (!profileCell.mxkImageView.gestureRecognizers.count) + { + // tap on avatar to update it + UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onProfileAvatarTap:)]; + [profileCell.mxkImageView addGestureRecognizer:tap]; + } + + profileCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_profile_picture", @"Vector", nil); + profileCell.accessibilityIdentifier=@"SettingsVCProfilPictureStaticText"; + profileCell.mxkLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + + // if the user defines a new avatar + if (newAvatarImage) + { + profileCell.mxkImageView.image = newAvatarImage; + } + else + { + UIImage* avatarImage = [AvatarGenerator generateAvatarForMatrixItem:myUser.userId withDisplayName:myUser.displayname]; + + if (myUser.avatarUrl) + { + profileCell.mxkImageView.enableInMemoryCache = YES; + + [profileCell.mxkImageView setImageURI:myUser.avatarUrl + withType:nil + andImageOrientation:UIImageOrientationUp + toFitViewSize:profileCell.mxkImageView.frame.size + withMethod:MXThumbnailingMethodCrop + previewImage:avatarImage + mediaManager:session.mediaManager]; + } + else + { + profileCell.mxkImageView.image = avatarImage; + } + } + + cell = profileCell; + } + else if (row == userSettingsDisplayNameIndex) + { + MXKTableViewCellWithLabelAndTextField *displaynameCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; + + displaynameCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_display_name", @"Vector", nil); + displaynameCell.mxkTextField.text = myUser.displayname; + + displaynameCell.mxkTextField.tag = row; + displaynameCell.mxkTextField.delegate = self; + [displaynameCell.mxkTextField removeTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; + [displaynameCell.mxkTextField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; + displaynameCell.mxkTextField.accessibilityIdentifier=@"SettingsVCDisplayNameTextField"; + + cell = displaynameCell; + } + else if (row == userSettingsFirstNameIndex) + { + MXKTableViewCellWithLabelAndTextField *firstCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; + + firstCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_first_name", @"Vector", nil); + firstCell.mxkTextField.userInteractionEnabled = NO; + + cell = firstCell; + } + else if (row == userSettingsSurnameIndex) + { + MXKTableViewCellWithLabelAndTextField *surnameCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; + + surnameCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_surname", @"Vector", nil); + surnameCell.mxkTextField.userInteractionEnabled = NO; + + cell = surnameCell; + } + else if (userSettingsEmailStartIndex <= row && row < userSettingsNewEmailIndex) + { + MXKTableViewCellWithLabelAndTextField *emailCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; + + emailCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_email_address", @"Vector", nil); + emailCell.mxkTextField.text = account.linkedEmails[row - userSettingsEmailStartIndex]; + emailCell.mxkTextField.userInteractionEnabled = NO; + + cell = emailCell; + } + else if (row == userSettingsNewEmailIndex) + { + MXKTableViewCellWithLabelAndTextField *newEmailCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; + + // Render the cell according to the `newEmailEditingEnabled` property + if (!_newEmailEditingEnabled) + { + newEmailCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_add_email_address", @"Vector", nil); + newEmailCell.mxkTextField.text = nil; + newEmailCell.mxkTextField.userInteractionEnabled = NO; + + newEmailCell.accessoryView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"plus_icon"]]; + } + else + { + newEmailCell.mxkLabel.text = nil; + newEmailCell.mxkTextField.placeholder = NSLocalizedStringFromTable(@"settings_email_address_placeholder", @"Vector", nil); + newEmailCell.mxkTextField.attributedPlaceholder = [[NSAttributedString alloc] + initWithString:newEmailCell.mxkTextField.placeholder + attributes:@{NSForegroundColorAttributeName: ThemeService.shared.theme.placeholderTextColor}]; + newEmailCell.mxkTextField.text = newEmailTextField.text; + newEmailCell.mxkTextField.userInteractionEnabled = YES; + newEmailCell.mxkTextField.keyboardType = UIKeyboardTypeEmailAddress; + newEmailCell.mxkTextField.autocorrectionType = UITextAutocorrectionTypeNo; + newEmailCell.mxkTextField.spellCheckingType = UITextSpellCheckingTypeNo; + newEmailCell.mxkTextField.delegate = self; + newEmailCell.mxkTextField.accessibilityIdentifier=@"SettingsVCAddEmailTextField"; + + [newEmailCell.mxkTextField removeTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; + [newEmailCell.mxkTextField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; + + [newEmailCell.mxkTextField removeTarget:self action:@selector(textFieldDidEnd:) forControlEvents:UIControlEventEditingDidEnd]; + [newEmailCell.mxkTextField addTarget:self action:@selector(textFieldDidEnd:) forControlEvents:UIControlEventEditingDidEnd]; + + // When displaying the textfield the 1st time, open the keyboard + if (!newEmailTextField) + { + newEmailTextField = newEmailCell.mxkTextField; + [self editNewEmailTextField]; + } + else + { + // Update the current text field. + newEmailTextField = newEmailCell.mxkTextField; + } + + UIImage *accessoryViewImage = [MXKTools paintImage:[UIImage imageNamed:@"plus_icon"] withColor:ThemeService.shared.theme.tintColor]; + newEmailCell.accessoryView = [[UIImageView alloc] initWithImage:accessoryViewImage]; + } + + newEmailCell.mxkTextField.tag = row; + + cell = newEmailCell; + } + else if (userSettingsPhoneStartIndex <= row && row < userSettingsNewPhoneIndex) + { + MXKTableViewCellWithLabelAndTextField *phoneCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; + + phoneCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_phone_number", @"Vector", nil); + + phoneCell.mxkTextField.text = [MXKTools readableMSISDN:account.linkedPhoneNumbers[row - userSettingsPhoneStartIndex]]; + phoneCell.mxkTextField.userInteractionEnabled = NO; + + cell = phoneCell; + } + else if (row == userSettingsNewPhoneIndex) + { + // Render the cell according to the `newPhoneEditingEnabled` property + if (!_newPhoneEditingEnabled) + { + MXKTableViewCellWithLabelAndTextField *newPhoneCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; + + newPhoneCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_add_phone_number", @"Vector", nil); + newPhoneCell.mxkTextField.text = nil; + newPhoneCell.mxkTextField.userInteractionEnabled = NO; + newPhoneCell.accessoryView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"plus_icon"]]; + + cell = newPhoneCell; + } + else + { + TableViewCellWithPhoneNumberTextField * newPhoneCell = [self.tableView dequeueReusableCellWithIdentifier:[TableViewCellWithPhoneNumberTextField defaultReuseIdentifier] forIndexPath:indexPath]; + + [newPhoneCell.countryCodeButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; + [newPhoneCell.countryCodeButton addTarget:self action:@selector(selectPhoneNumberCountry:) forControlEvents:UIControlEventTouchUpInside]; + newPhoneCell.countryCodeButton.accessibilityIdentifier = @"SettingsVCPhoneCountryButton"; + + newPhoneCell.mxkLabel.font = newPhoneCell.mxkTextField.font = [UIFont systemFontOfSize:16]; + + newPhoneCell.mxkTextField.userInteractionEnabled = YES; + newPhoneCell.mxkTextField.keyboardType = UIKeyboardTypePhonePad; + newPhoneCell.mxkTextField.autocorrectionType = UITextAutocorrectionTypeNo; + newPhoneCell.mxkTextField.spellCheckingType = UITextSpellCheckingTypeNo; + newPhoneCell.mxkTextField.delegate = self; + newPhoneCell.mxkTextField.accessibilityIdentifier=@"SettingsVCAddPhoneTextField"; + + [newPhoneCell.mxkTextField removeTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; + [newPhoneCell.mxkTextField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; + + [newPhoneCell.mxkTextField removeTarget:self action:@selector(textFieldDidEnd:) forControlEvents:UIControlEventEditingDidEnd]; + [newPhoneCell.mxkTextField addTarget:self action:@selector(textFieldDidEnd:) forControlEvents:UIControlEventEditingDidEnd]; + + newPhoneCell.mxkTextField.tag = row; + + // When displaying the textfield the 1st time, open the keyboard + if (!newPhoneNumberCell) + { + NSString *countryCode = [MXKAppSettings standardAppSettings].phonebookCountryCode; + if (!countryCode) + { + // If none, consider the preferred locale + NSLocale *local = [[NSLocale alloc] initWithLocaleIdentifier:[[NSBundle mainBundle] preferredLocalizations][0]]; + if ([local respondsToSelector:@selector(countryCode)]) + { + countryCode = local.countryCode; + } + + if (!countryCode) + { + countryCode = @"GB"; + } + } + newPhoneCell.isoCountryCode = countryCode; + newPhoneCell.mxkTextField.text = nil; + + newPhoneNumberCell = newPhoneCell; + + [self editNewPhoneNumberTextField]; + } + else + { + newPhoneCell.isoCountryCode = newPhoneNumberCell.isoCountryCode; + newPhoneCell.mxkTextField.text = newPhoneNumberCell.mxkTextField.text; + + newPhoneNumberCell = newPhoneCell; + } + + UIImage *accessoryViewImage = [MXKTools paintImage:[UIImage imageNamed:@"plus_icon"] withColor:ThemeService.shared.theme.tintColor]; + newPhoneCell.accessoryView = [[UIImageView alloc] initWithImage:accessoryViewImage]; + + cell = newPhoneCell; + } + } + else if (row == userSettingsThreePidsInformation) + { + MXKTableViewCell *threePidsInformationCell = [self getDefaultTableViewCell:self.tableView]; + + NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"settings_three_pids_management_information_part1", @"Vector", nil) attributes:@{NSForegroundColorAttributeName: ThemeService.shared.theme.textPrimaryColor}]; + [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"settings_three_pids_management_information_part2", @"Vector", nil) attributes:@{NSForegroundColorAttributeName: ThemeService.shared.theme.tintColor}]]; + [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"settings_three_pids_management_information_part3", @"Vector", nil) attributes:@{NSForegroundColorAttributeName: ThemeService.shared.theme.textPrimaryColor}]]; + + threePidsInformationCell.textLabel.attributedText = attributedString; + threePidsInformationCell.textLabel.numberOfLines = 0; + + threePidsInformationCell.selectionStyle = UITableViewCellSelectionStyleNone; + + cell = threePidsInformationCell; + } + else if (row == userSettingsChangePasswordIndex) + { + MXKTableViewCellWithLabelAndTextField *passwordCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; + + passwordCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_change_password", @"Vector", nil); + passwordCell.mxkTextField.text = @"*********"; + passwordCell.mxkTextField.userInteractionEnabled = NO; + passwordCell.mxkLabel.accessibilityIdentifier=@"SettingsVCChangePwdStaticText"; + + cell = passwordCell; + } + else if (row == userSettingsNightModeSepIndex) + { + UITableViewCell *sepCell = [[UITableViewCell alloc] init]; + sepCell.backgroundColor = ThemeService.shared.theme.headerBackgroundColor; + + cell = sepCell; + } + else if (row == userSettingsNightModeIndex) + { + MXKTableViewCellWithLabelAndTextField *nightModeCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; + + nightModeCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_night_mode", @"Vector", nil); + nightModeCell.mxkTextField.userInteractionEnabled = NO; + nightModeCell.mxkTextField.text = NSLocalizedStringFromTable(@"off", @"Vector", nil); + nightModeCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + cell = nightModeCell; + } + } + else if (section == SETTINGS_SECTION_NOTIFICATIONS_SETTINGS_INDEX) + { + if (row == NOTIFICATION_SETTINGS_ENABLE_PUSH_INDEX) + { + MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + + labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_enable_push_notif", @"Vector", nil); + labelAndSwitchCell.mxkSwitch.on = account.isPushKitNotificationActive; + labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + labelAndSwitchCell.mxkSwitch.enabled = YES; + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(togglePushNotifications:) forControlEvents:UIControlEventTouchUpInside]; + + cell = labelAndSwitchCell; + } + else if (row == NOTIFICATION_SETTINGS_SHOW_DECODED_CONTENT) + { + MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + + labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_show_decrypted_content", @"Vector", nil); + labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.showDecryptedContentInNotifications; + labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + labelAndSwitchCell.mxkSwitch.enabled = account.isPushKitNotificationActive; + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleShowDecodedContent:) forControlEvents:UIControlEventTouchUpInside]; + + + cell = labelAndSwitchCell; + } + else if (row == NOTIFICATION_SETTINGS_GLOBAL_SETTINGS_INDEX) + { + MXKTableViewCell *globalInfoCell = [self getDefaultTableViewCell:tableView]; + + NSString *appDisplayName = [[NSBundle mainBundle] infoDictionary][@"CFBundleDisplayName"]; + + globalInfoCell.textLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"settings_global_settings_info", @"Vector", nil), appDisplayName]; + globalInfoCell.textLabel.numberOfLines = 0; + + globalInfoCell.selectionStyle = UITableViewCellSelectionStyleNone; + + cell = globalInfoCell; + } + else if (row == NOTIFICATION_SETTINGS_PIN_MISSED_NOTIFICATIONS_INDEX) + { + MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + + labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_pin_rooms_with_missed_notif", @"Vector", nil); + labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.pinRoomsWithMissedNotificationsOnHome; + labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + labelAndSwitchCell.mxkSwitch.enabled = YES; + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(togglePinRoomsWithMissedNotif:) forControlEvents:UIControlEventTouchUpInside]; + + cell = labelAndSwitchCell; + } + else if (row == NOTIFICATION_SETTINGS_PIN_UNREAD_INDEX) + { + MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + + labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_pin_rooms_with_unread", @"Vector", nil); + labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.pinRoomsWithUnreadMessagesOnHome; + labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + labelAndSwitchCell.mxkSwitch.enabled = YES; + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(togglePinRoomsWithUnread:) forControlEvents:UIControlEventTouchUpInside]; + + cell = labelAndSwitchCell; + } + } + else if (section == SETTINGS_SECTION_CALLS_INDEX) + { + if (row == CALLS_ENABLE_CALLKIT_INDEX) + { + MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_enable_callkit", @"Vector", nil); + labelAndSwitchCell.mxkSwitch.on = [MXKAppSettings standardAppSettings].isCallKitEnabled; + labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + labelAndSwitchCell.mxkSwitch.enabled = YES; + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleCallKit:) forControlEvents:UIControlEventTouchUpInside]; + + if (![MXCallKitAdapter callKitAvailable]) + { + labelAndSwitchCell.mxkSwitch.on = NO; + labelAndSwitchCell.mxkSwitch.enabled = NO; + labelAndSwitchCell.mxkLabel.enabled = NO; + } + + cell = labelAndSwitchCell; + } + else if (row == CALLS_CALLKIT_DESCRIPTION_INDEX) + { + MXKTableViewCell *globalInfoCell = [self getDefaultTableViewCell:tableView]; + globalInfoCell.textLabel.text = NSLocalizedStringFromTable(@"settings_callkit_info", @"Vector", nil); + globalInfoCell.textLabel.numberOfLines = 0; + globalInfoCell.selectionStyle = UITableViewCellSelectionStyleNone; + + if (![MXCallKitAdapter callKitAvailable]) + { + globalInfoCell.textLabel.enabled = NO; + } + + cell = globalInfoCell; + } + else if (row == CALLS_ENABLE_STUN_SERVER_FALLBACK_INDEX) + { + MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_calls_stun_server_fallback_button", @"Vector", nil); + labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.allowStunServerFallback; + labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + labelAndSwitchCell.mxkSwitch.enabled = YES; + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleStunServerFallback:) forControlEvents:UIControlEventTouchUpInside]; + + cell = labelAndSwitchCell; + } + else if (row == CALLS_STUN_SERVER_FALLBACK_DESCRIPTION_INDEX) + { + NSString *stunFallbackHost = RiotSettings.shared.stunServerFallback; + // Remove "stun:" + stunFallbackHost = [stunFallbackHost componentsSeparatedByString:@":"].lastObject; + + MXKTableViewCell *globalInfoCell = [self getDefaultTableViewCell:tableView]; + globalInfoCell.textLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"settings_calls_stun_server_fallback_description", @"Vector", nil), stunFallbackHost]; + globalInfoCell.textLabel.numberOfLines = 0; + globalInfoCell.selectionStyle = UITableViewCellSelectionStyleNone; + + cell = globalInfoCell; + } + } + else if (section == SETTINGS_SECTION_DISCOVERY_INDEX) + { + cell = [self.settingsDiscoveryTableViewSection cellForRowAtRow:row]; + } + else if (section == SETTINGS_SECTION_IDENTITY_SERVER_INDEX) + { + switch (row) + { + case IDENTITY_SERVER_INDEX: + { + MXKTableViewCell *isCell = [self getDefaultTableViewCell:tableView]; + + if (account.mxSession.identityService.identityServer) + { + isCell.textLabel.text = account.mxSession.identityService.identityServer; + } + else + { + isCell.textLabel.text = NSLocalizedStringFromTable(@"settings_identity_server_no_is", @"Vector", nil); + } + isCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + cell = isCell; + break; + } + + case IDENTITY_SERVER_DESCRIPTION_INDEX: + { + MXKTableViewCell *descriptionCell = [self getDefaultTableViewCell:tableView]; + + if (account.mxSession.identityService.identityServer) + { + descriptionCell.textLabel.text = NSLocalizedStringFromTable(@"settings_identity_server_description", @"Vector", nil); + } + else + { + descriptionCell.textLabel.text = NSLocalizedStringFromTable(@"settings_identity_server_no_is_description", @"Vector", nil); + } + descriptionCell.textLabel.numberOfLines = 0; + descriptionCell.selectionStyle = UITableViewCellSelectionStyleNone; + + cell = descriptionCell; + break; + } + + default: + break; + } + } + else if (section == SETTINGS_SECTION_INTEGRATIONS_INDEX) + { + switch (row) { + case INTEGRATIONS_INDEX: + { + RiotSharedSettings *sharedSettings = [[RiotSharedSettings alloc] initWithSession:session]; + + MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_integrations_allow_button", @"Vector", nil); + labelAndSwitchCell.mxkSwitch.on = sharedSettings.hasIntegrationProvisioningEnabled; + labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + labelAndSwitchCell.mxkSwitch.enabled = YES; + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleAllowIntegrations:) forControlEvents:UIControlEventTouchUpInside]; + + cell = labelAndSwitchCell; + break; + } + + case INTEGRATIONS_DESCRIPTION_INDEX: + { + MXKTableViewCell *descriptionCell = [self getDefaultTableViewCell:tableView]; + + NSString *integrationManager = [WidgetManager.sharedManager configForUser:session.myUser.userId].apiUrl; + NSString *integrationManagerDomain = [NSURL URLWithString:integrationManager].host; + + NSString *description = [NSString stringWithFormat:NSLocalizedStringFromTable(@"settings_integrations_allow_description", @"Vector", nil), integrationManagerDomain]; + descriptionCell.textLabel.text = description; + descriptionCell.textLabel.numberOfLines = 0; + descriptionCell.selectionStyle = UITableViewCellSelectionStyleNone; + + cell = descriptionCell; + break; + } + + default: + break; + } + } + else if (section == SETTINGS_SECTION_USER_INTERFACE_INDEX) + { + if (row == USER_INTERFACE_LANGUAGE_INDEX) + { + cell = [tableView dequeueReusableCellWithIdentifier:kSettingsViewControllerPhoneBookCountryCellId2]; + if (!cell) + { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:kSettingsViewControllerPhoneBookCountryCellId2]; + } + + NSString *language = [NSBundle mxk_language]; + if (!language) + { + language = [MXKLanguagePickerViewController defaultLanguage]; + } + NSString *languageDescription = [MXKLanguagePickerViewController languageDescription:language]; + + // Capitalise the description in the language locale + NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:language]; + languageDescription = [languageDescription capitalizedStringWithLocale:locale]; + + cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + + cell.textLabel.text = NSLocalizedStringFromTable(@"settings_ui_language", @"Vector", nil); + cell.detailTextLabel.text = languageDescription; + + cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + cell.selectionStyle = UITableViewCellSelectionStyleDefault; + } + else if (row == USER_INTERFACE_THEME_INDEX) + { + cell = [tableView dequeueReusableCellWithIdentifier:kSettingsViewControllerPhoneBookCountryCellId2]; + if (!cell) + { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:kSettingsViewControllerPhoneBookCountryCellId2]; + } + + NSString *theme = RiotSettings.shared.userInterfaceTheme; + + if (!theme) + { + if (@available(iOS 11.0, *)) + { + // "auto" is used the default value from iOS 11 + theme = @"auto"; + } + else + { + // Use "light" for older version + theme = @"light"; + } + } + + theme = [NSString stringWithFormat:@"settings_ui_theme_%@", theme]; + NSString *i18nTheme = NSLocalizedStringFromTable(theme, + @"Vector", + nil); + + cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + + cell.textLabel.text = NSLocalizedStringFromTable(@"settings_ui_theme", @"Vector", nil); + cell.detailTextLabel.text = i18nTheme; + + cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + cell.selectionStyle = UITableViewCellSelectionStyleDefault; + } + } + else if (section == SETTINGS_SECTION_IGNORED_USERS_INDEX) + { + MXKTableViewCell *ignoredUserCell = [self getDefaultTableViewCell:tableView]; + + NSString *ignoredUserId; + if (indexPath.row < session.ignoredUsers.count) + { + ignoredUserId = session.ignoredUsers[indexPath.row]; + } + ignoredUserCell.textLabel.text = ignoredUserId; + + cell = ignoredUserCell; + } + else if (section == SETTINGS_SECTION_CONTACTS_INDEX) + { + if (row == localContactsSyncIndex) + { + MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + + labelAndSwitchCell.mxkLabel.numberOfLines = 0; + labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_contacts_discover_matrix_users", @"Vector", nil); + labelAndSwitchCell.mxkSwitch.on = [MXKAppSettings standardAppSettings].syncLocalContacts; + labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + labelAndSwitchCell.mxkSwitch.enabled = YES; + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleLocalContactsSync:) forControlEvents:UIControlEventTouchUpInside]; + + cell = labelAndSwitchCell; + } + else if (row == localContactsPhoneBookCountryIndex) + { + cell = [tableView dequeueReusableCellWithIdentifier:kSettingsViewControllerPhoneBookCountryCellId2]; + if (!cell) + { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:kSettingsViewControllerPhoneBookCountryCellId2]; + } + + NSString* countryCode = [[MXKAppSettings standardAppSettings] phonebookCountryCode]; + NSLocale *local = [[NSLocale alloc] initWithLocaleIdentifier:[[NSBundle mainBundle] preferredLocalizations][0]]; + NSString *countryName = [local displayNameForKey:NSLocaleCountryCode value:countryCode]; + + cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + + cell.textLabel.text = NSLocalizedStringFromTable(@"settings_contacts_phonebook_country", @"Vector", nil); + cell.detailTextLabel.text = countryName; + + cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + cell.selectionStyle = UITableViewCellSelectionStyleDefault; + } + } + else if (section == SETTINGS_SECTION_ADVANCED_INDEX) + { + MXKTableViewCellWithTextView *configCell = [self textViewCellForTableView:tableView atIndexPath:indexPath]; + + NSString *configFormat = [NSString stringWithFormat:@"%@\n%@\n%@", [NSBundle mxk_localizedStringForKey:@"settings_config_user_id"], [NSBundle mxk_localizedStringForKey:@"settings_config_home_server"], [NSBundle mxk_localizedStringForKey:@"settings_config_identity_server"]]; + + configCell.mxkTextView.text =[NSString stringWithFormat:configFormat, account.mxCredentials.userId, account.mxCredentials.homeServer, account.identityServerURL]; + configCell.mxkTextView.accessibilityIdentifier=@"SettingsVCConfigStaticText"; + + cell = configCell; + } + else if (section == SETTINGS_SECTION_OTHER_INDEX) + { + if (row == OTHER_VERSION_INDEX) + { + MXKTableViewCell *versionCell = [self getDefaultTableViewCell:tableView]; + + NSString* appVersion = [AppDelegate theDelegate].appVersion; + NSString* build = [AppDelegate theDelegate].build; + + versionCell.textLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"settings_version", @"Vector", nil), [NSString stringWithFormat:@"%@ %@", appVersion, build]]; + + versionCell.selectionStyle = UITableViewCellSelectionStyleNone; + + cell = versionCell; + } + else if (row == OTHER_OLM_VERSION_INDEX) + { + MXKTableViewCell *versionCell = [self getDefaultTableViewCell:tableView]; + + versionCell.textLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"settings_olm_version", @"Vector", nil), [OLMKit versionString]]; + + versionCell.selectionStyle = UITableViewCellSelectionStyleNone; + + cell = versionCell; + } + else if (row == OTHER_TERM_CONDITIONS_INDEX) + { + MXKTableViewCell *termAndConditionCell = [self getDefaultTableViewCell:tableView]; + + termAndConditionCell.textLabel.text = NSLocalizedStringFromTable(@"settings_term_conditions", @"Vector", nil); + + termAndConditionCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + + cell = termAndConditionCell; + } + else if (row == OTHER_COPYRIGHT_INDEX) + { + MXKTableViewCell *copyrightCell = [self getDefaultTableViewCell:tableView]; + + copyrightCell.textLabel.text = NSLocalizedStringFromTable(@"settings_copyright", @"Vector", nil); + + copyrightCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + + cell = copyrightCell; + } + else if (row == OTHER_PRIVACY_INDEX) + { + MXKTableViewCell *privacyPolicyCell = [self getDefaultTableViewCell:tableView]; + + privacyPolicyCell.textLabel.text = NSLocalizedStringFromTable(@"settings_privacy_policy", @"Vector", nil); + + privacyPolicyCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + + cell = privacyPolicyCell; + } + else if (row == OTHER_THIRD_PARTY_INDEX) + { + MXKTableViewCell *thirdPartyCell = [self getDefaultTableViewCell:tableView]; + + thirdPartyCell.textLabel.text = NSLocalizedStringFromTable(@"settings_third_party_notices", @"Vector", nil); + + thirdPartyCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + + cell = thirdPartyCell; + } + else if (row == OTHER_CRASH_REPORT_INDEX) + { + MXKTableViewCellWithLabelAndSwitch* sendCrashReportCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + + sendCrashReportCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_send_crash_report", @"Vector", nil); + sendCrashReportCell.mxkSwitch.on = RiotSettings.shared.enableCrashReport; + sendCrashReportCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + sendCrashReportCell.mxkSwitch.enabled = YES; + [sendCrashReportCell.mxkSwitch addTarget:self action:@selector(toggleSendCrashReport:) forControlEvents:UIControlEventTouchUpInside]; + + cell = sendCrashReportCell; + } + else if (row == OTHER_ENABLE_RAGESHAKE_INDEX) + { + MXKTableViewCellWithLabelAndSwitch* enableRageShakeCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + + enableRageShakeCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_enable_rageshake", @"Vector", nil); + enableRageShakeCell.mxkSwitch.on = RiotSettings.shared.enableRageShake; + enableRageShakeCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + enableRageShakeCell.mxkSwitch.enabled = YES; + [enableRageShakeCell.mxkSwitch addTarget:self action:@selector(toggleEnableRageShake:) forControlEvents:UIControlEventTouchUpInside]; + + cell = enableRageShakeCell; + } + else if (row == OTHER_MARK_ALL_AS_READ_INDEX) + { + MXKTableViewCellWithButton *markAllBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; + if (!markAllBtnCell) + { + markAllBtnCell = [[MXKTableViewCellWithButton alloc] init]; + } + else + { + // Fix https://github.com/vector-im/riot-ios/issues/1354 + markAllBtnCell.mxkButton.titleLabel.text = nil; + } + + NSString *btnTitle = NSLocalizedStringFromTable(@"settings_mark_all_as_read", @"Vector", nil); + [markAllBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; + [markAllBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; + [markAllBtnCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; + markAllBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; + + [markAllBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; + [markAllBtnCell.mxkButton addTarget:self action:@selector(markAllAsRead:) forControlEvents:UIControlEventTouchUpInside]; + markAllBtnCell.mxkButton.accessibilityIdentifier = nil; + + cell = markAllBtnCell; + } + else if (row == OTHER_CLEAR_CACHE_INDEX) + { + MXKTableViewCellWithButton *clearCacheBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; + if (!clearCacheBtnCell) + { + clearCacheBtnCell = [[MXKTableViewCellWithButton alloc] init]; + } + else + { + // Fix https://github.com/vector-im/riot-ios/issues/1354 + clearCacheBtnCell.mxkButton.titleLabel.text = nil; + } + + NSString *btnTitle = NSLocalizedStringFromTable(@"settings_clear_cache", @"Vector", nil); + [clearCacheBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; + [clearCacheBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; + [clearCacheBtnCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; + clearCacheBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; + + [clearCacheBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; + [clearCacheBtnCell.mxkButton addTarget:self action:@selector(clearCache:) forControlEvents:UIControlEventTouchUpInside]; + clearCacheBtnCell.mxkButton.accessibilityIdentifier = nil; + + cell = clearCacheBtnCell; + } + else if (row == OTHER_REPORT_BUG_INDEX) + { + MXKTableViewCellWithButton *reportBugBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; + if (!reportBugBtnCell) + { + reportBugBtnCell = [[MXKTableViewCellWithButton alloc] init]; + } + else + { + // Fix https://github.com/vector-im/riot-ios/issues/1354 + reportBugBtnCell.mxkButton.titleLabel.text = nil; + } + + NSString *btnTitle = NSLocalizedStringFromTable(@"settings_report_bug", @"Vector", nil); + [reportBugBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; + [reportBugBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; + [reportBugBtnCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; + reportBugBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; + + [reportBugBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; + [reportBugBtnCell.mxkButton addTarget:self action:@selector(reportBug:) forControlEvents:UIControlEventTouchUpInside]; + reportBugBtnCell.mxkButton.accessibilityIdentifier = nil; + + cell = reportBugBtnCell; + } + } + else if (section == SETTINGS_SECTION_LABS_INDEX) + { + if (row == LABS_USE_ROOM_MEMBERS_LAZY_LOADING_INDEX) + { + MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + + labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_labs_room_members_lazy_loading", @"Vector", nil); + + MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; + labelAndSwitchCell.mxkSwitch.on = account.mxSession.syncWithLazyLoadOfRoomMembers; + labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleSyncWithLazyLoadOfRoomMembers:) forControlEvents:UIControlEventTouchUpInside]; + + cell = labelAndSwitchCell; + } + else if (row == LABS_USE_JITSI_WIDGET_INDEX) + { + MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + + labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_labs_create_conference_with_jitsi", @"Vector", nil); + labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.createConferenceCallsWithJitsi; + labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleJitsiForConference:) forControlEvents:UIControlEventTouchUpInside]; + + cell = labelAndSwitchCell; + } + else if (row == LABS_CROSS_SIGNING_INDEX) + { + MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + + labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_labs_enable_cross_signing", @"Vector", nil); + labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.enableCrossSigning; + labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + labelAndSwitchCell.mxkSwitch.enabled = YES; + + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleLabsCrossSigning:) forControlEvents:UIControlEventTouchUpInside]; + + cell = labelAndSwitchCell; + } + } + else if (section == SETTINGS_SECTION_FLAIR_INDEX) + { + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:groupsDataSource.joinedGroupsSection]; + cell = [groupsDataSource tableView:tableView cellForRowAtIndexPath:indexPath]; + + if ([cell isKindOfClass:GroupTableViewCellWithSwitch.class]) + { + GroupTableViewCellWithSwitch* groupWithSwitchCell = (GroupTableViewCellWithSwitch*)cell; + id groupCellData = [groupsDataSource cellDataAtIndex:indexPath]; + + // Display the groupId in the description label, except if the group has no name + if (![groupWithSwitchCell.groupName.text isEqualToString:groupCellData.group.groupId]) + { + groupWithSwitchCell.groupDescription.hidden = NO; + groupWithSwitchCell.groupDescription.text = groupCellData.group.groupId; + } + + // Update the toogle button + groupWithSwitchCell.toggleButton.on = groupCellData.group.summary.user.isPublicised; + groupWithSwitchCell.toggleButton.enabled = YES; + groupWithSwitchCell.toggleButton.tag = row; + + [groupWithSwitchCell.toggleButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; + [groupWithSwitchCell.toggleButton addTarget:self action:@selector(toggleCommunityFlair:) forControlEvents:UIControlEventTouchUpInside]; + } + } + else if (section == SETTINGS_SECTION_DEVICES_INDEX) + { + if (row == DEVICES_DESCRIPTION_INDEX) + { + MXKTableViewCell *descriptionCell = [self getDefaultTableViewCell:tableView]; + descriptionCell.textLabel.text = NSLocalizedStringFromTable(@"settings_devices_description", @"Vector", nil); + descriptionCell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + descriptionCell.textLabel.font = [UIFont systemFontOfSize:15]; + descriptionCell.textLabel.numberOfLines = 0; + descriptionCell.contentView.backgroundColor = ThemeService.shared.theme.headerBackgroundColor; + descriptionCell.selectionStyle = UITableViewCellSelectionStyleNone; + + cell = descriptionCell; + } + else + { + NSUInteger deviceIndex = row - 1; + + MXKTableViewCell *deviceCell = [self getDefaultTableViewCell:tableView]; + if (deviceIndex < devicesArray.count) + { + NSString *name = devicesArray[deviceIndex].displayName; + NSString *deviceId = devicesArray[deviceIndex].deviceId; + deviceCell.textLabel.text = (name.length ? [NSString stringWithFormat:@"%@ (%@)", name, deviceId] : [NSString stringWithFormat:@"(%@)", deviceId]); + deviceCell.textLabel.numberOfLines = 0; + + if ([deviceId isEqualToString:self.mainSession.matrixRestClient.credentials.deviceId]) + { + deviceCell.textLabel.font = [UIFont boldSystemFontOfSize:17]; + } + } + + cell = deviceCell; + } + + } + else if (section == SETTINGS_SECTION_CRYPTOGRAPHY_INDEX) + { + if (row == CRYPTOGRAPHY_INFO_INDEX) + { + MXKTableViewCellWithTextView *cryptoCell = [self textViewCellForTableView:tableView atIndexPath:indexPath]; + + cryptoCell.mxkTextView.attributedText = [self cryptographyInformation]; + + cell = cryptoCell; + } + else if (row == CRYPTOGRAPHY_BLACKLIST_UNVERIFIED_DEVICES_INDEX) + { + MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + + labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_crypto_blacklist_unverified_devices", @"Vector", nil); + labelAndSwitchCell.mxkSwitch.on = account.mxSession.crypto.globalBlacklistUnverifiedDevices; + labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + labelAndSwitchCell.mxkSwitch.enabled = YES; + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleBlacklistUnverifiedDevices:) forControlEvents:UIControlEventTouchUpInside]; + + cell = labelAndSwitchCell; + } + else if (row == CRYPTOGRAPHY_EXPORT_INDEX) + { + MXKTableViewCellWithButton *exportKeysBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; + if (!exportKeysBtnCell) + { + exportKeysBtnCell = [[MXKTableViewCellWithButton alloc] init]; + } + else + { + // Fix https://github.com/vector-im/riot-ios/issues/1354 + exportKeysBtnCell.mxkButton.titleLabel.text = nil; + } + + NSString *btnTitle = NSLocalizedStringFromTable(@"settings_crypto_export", @"Vector", nil); + [exportKeysBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; + [exportKeysBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; + [exportKeysBtnCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; + exportKeysBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; + + [exportKeysBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; + [exportKeysBtnCell.mxkButton addTarget:self action:@selector(exportEncryptionKeys:) forControlEvents:UIControlEventTouchUpInside]; + exportKeysBtnCell.mxkButton.accessibilityIdentifier = nil; + + cell = exportKeysBtnCell; + } + } + else if (section == SETTINGS_SECTION_KEYBACKUP_INDEX) + { + cell = [keyBackupSection cellForRowAtRow:row]; + } + else if (section == SETTINGS_SECTION_DEACTIVATE_ACCOUNT_INDEX) + { + MXKTableViewCellWithButton *deactivateAccountBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; + + if (!deactivateAccountBtnCell) + { + deactivateAccountBtnCell = [[MXKTableViewCellWithButton alloc] init]; + } + else + { + // Fix https://github.com/vector-im/riot-ios/issues/1354 + deactivateAccountBtnCell.mxkButton.titleLabel.text = nil; + } + + NSString *btnTitle = NSLocalizedStringFromTable(@"settings_deactivate_my_account", @"Vector", nil); + [deactivateAccountBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; + [deactivateAccountBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; + [deactivateAccountBtnCell.mxkButton setTintColor:ThemeService.shared.theme.warningColor]; + deactivateAccountBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; + + [deactivateAccountBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; + [deactivateAccountBtnCell.mxkButton addTarget:self action:@selector(deactivateAccountAction) forControlEvents:UIControlEventTouchUpInside]; + deactivateAccountBtnCell.mxkButton.accessibilityIdentifier = nil; + + cell = deactivateAccountBtnCell; + } + + return cell; +} + +- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section +{ + if (section == SETTINGS_SECTION_USER_SETTINGS_INDEX) + { + return NSLocalizedStringFromTable(@"settings_user_settings", @"Vector", nil); + } + else if (section == SETTINGS_SECTION_NOTIFICATIONS_SETTINGS_INDEX) + { + return NSLocalizedStringFromTable(@"settings_notifications_settings", @"Vector", nil); + } + else if (section == SETTINGS_SECTION_CALLS_INDEX) + { + return NSLocalizedStringFromTable(@"settings_calls_settings", @"Vector", nil); + } + else if (section == SETTINGS_SECTION_DISCOVERY_INDEX) + { + return NSLocalizedStringFromTable(@"settings_discovery_settings", @"Vector", nil); + } + else if (section == SETTINGS_SECTION_IDENTITY_SERVER_INDEX) + { + return NSLocalizedStringFromTable(@"settings_identity_server_settings", @"Vector", nil); + } + else if (section == SETTINGS_SECTION_INTEGRATIONS_INDEX) + { + return NSLocalizedStringFromTable(@"settings_integrations", @"Vector", nil); + } + else if (section == SETTINGS_SECTION_USER_INTERFACE_INDEX) + { + return NSLocalizedStringFromTable(@"settings_user_interface", @"Vector", nil); + } + else if (section == SETTINGS_SECTION_IGNORED_USERS_INDEX) + { + // Check whether this section is visible + if ([AppDelegate theDelegate].mxSessions.count > 0) + { + MXSession* session = [AppDelegate theDelegate].mxSessions[0]; + if (session.ignoredUsers.count) + { + return NSLocalizedStringFromTable(@"settings_ignored_users", @"Vector", nil); + } + } + } + else if (section == SETTINGS_SECTION_CONTACTS_INDEX) + { + return NSLocalizedStringFromTable(@"settings_contacts", @"Vector", nil); + } + else if (section == SETTINGS_SECTION_ADVANCED_INDEX) + { + return NSLocalizedStringFromTable(@"settings_advanced", @"Vector", nil); + } + else if (section == SETTINGS_SECTION_OTHER_INDEX) + { + return NSLocalizedStringFromTable(@"settings_other", @"Vector", nil); + } + else if (section == SETTINGS_SECTION_LABS_INDEX) + { + return NSLocalizedStringFromTable(@"settings_labs", @"Vector", nil); + } + else if (section == SETTINGS_SECTION_FLAIR_INDEX) + { + // Check whether this section is visible + if (groupsDataSource.joinedGroupsSection != -1) + { + return NSLocalizedStringFromTable(@"settings_flair", @"Vector", nil); + } + } + else if (section == SETTINGS_SECTION_DEVICES_INDEX) + { + // Check whether this section is visible + if (devicesArray.count > 0) + { + return NSLocalizedStringFromTable(@"settings_devices", @"Vector", nil); + } + } + else if (section == SETTINGS_SECTION_CRYPTOGRAPHY_INDEX) + { + // Check whether this section is visible + if (self.mainSession.crypto) + { + return NSLocalizedStringFromTable(@"settings_cryptography", @"Vector", nil); + } + } + else if (section == SETTINGS_SECTION_KEYBACKUP_INDEX) + { + // Check whether this section is visible + if (self.mainSession.crypto) + { + return NSLocalizedStringFromTable(@"settings_key_backup", @"Vector", nil); + } + } + else if (section == SETTINGS_SECTION_DEACTIVATE_ACCOUNT_INDEX) + { + return NSLocalizedStringFromTable(@"settings_deactivate_my_account", @"Vector", nil); + } + + return nil; +} + +- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section +{ + if ([view isKindOfClass:UITableViewHeaderFooterView.class]) + { + // Customize label style + UITableViewHeaderFooterView *tableViewHeaderFooterView = (UITableViewHeaderFooterView*)view; + tableViewHeaderFooterView.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + tableViewHeaderFooterView.textLabel.font = [UIFont systemFontOfSize:15]; + } +} + +- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (indexPath.section == SETTINGS_SECTION_USER_SETTINGS_INDEX) + { + NSInteger row = indexPath.row; + if ((userSettingsEmailStartIndex <= row && row < userSettingsNewEmailIndex) || + (userSettingsPhoneStartIndex <= row && row < userSettingsNewPhoneIndex)) + { + return YES; + } + } + return NO; +} + +- (void)tableView:(UITableView*)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath*)indexPath +{ + // iOS8 requires this method to enable editing (see editActionsForRowAtIndexPath). +} + +#pragma mark - UITableView delegate + +- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath; +{ + cell.backgroundColor = ThemeService.shared.theme.backgroundColor; + + if (cell.selectionStyle != UITableViewCellSelectionStyleNone) + { + // Update the selected background view + if (ThemeService.shared.theme.selectedBackgroundColor) + { + cell.selectedBackgroundView = [[UIView alloc] init]; + cell.selectedBackgroundView.backgroundColor = ThemeService.shared.theme.selectedBackgroundColor; + } + else + { + if (tableView.style == UITableViewStylePlain) + { + cell.selectedBackgroundView = nil; + } + else + { + cell.selectedBackgroundView.backgroundColor = nil; + } + } + } +} + +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section +{ + if (section == SETTINGS_SECTION_IGNORED_USERS_INDEX) + { + if ([AppDelegate theDelegate].mxSessions.count > 0) + { + MXSession* session = [AppDelegate theDelegate].mxSessions[0]; + if (session.ignoredUsers.count == 0) + { + // Hide this section + return SECTION_TITLE_PADDING_WHEN_HIDDEN; + } + } + } + else if (section == SETTINGS_SECTION_FLAIR_INDEX) + { + if (groupsDataSource.joinedGroupsSection == -1) + { + return SECTION_TITLE_PADDING_WHEN_HIDDEN; + } + } + + return 24; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section +{ + if (section == SETTINGS_SECTION_IGNORED_USERS_INDEX) + { + if ([AppDelegate theDelegate].mxSessions.count > 0) + { + MXSession* session = [AppDelegate theDelegate].mxSessions[0]; + if (session.ignoredUsers.count == 0) + { + // Hide this section + return SECTION_TITLE_PADDING_WHEN_HIDDEN; + } + } + } + else if (section == SETTINGS_SECTION_FLAIR_INDEX) + { + if (groupsDataSource.joinedGroupsSection == -1) + { + return SECTION_TITLE_PADDING_WHEN_HIDDEN; + } + } + + return 24; +} + +- (NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath +{ + NSMutableArray* actions; + + // Add the swipe to delete user's email or phone number + if (indexPath.section == SETTINGS_SECTION_USER_SETTINGS_INDEX) + { + NSInteger row = indexPath.row; + if ((userSettingsEmailStartIndex <= row && row < userSettingsNewEmailIndex) || + (userSettingsPhoneStartIndex <= row && row < userSettingsNewPhoneIndex)) + { + actions = [[NSMutableArray alloc] init]; + + UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; + CGFloat cellHeight = cell ? cell.frame.size.height : 50; + + // Patch: Force the width of the button by adding whitespace characters into the title string. + UITableViewRowAction *leaveAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive title:@" " handler:^(UITableViewRowAction *action, NSIndexPath *indexPath){ + + [self onRemove3PID:indexPath]; + + }]; + + leaveAction.backgroundColor = [MXKTools convertImageToPatternColor:@"remove_icon_pink" backgroundColor:ThemeService.shared.theme.headerBackgroundColor patternSize:CGSizeMake(50, cellHeight) resourceSize:CGSizeMake(24, 24)]; + [actions insertObject:leaveAction atIndex:0]; + } + } + + return actions; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (self.tableView == tableView) + { + NSInteger section = indexPath.section; + NSInteger row = indexPath.row; + + if (section == SETTINGS_SECTION_USER_INTERFACE_INDEX) + { + if (row == USER_INTERFACE_LANGUAGE_INDEX) + { + // Display the language picker + LanguagePickerViewController *languagePickerViewController = [LanguagePickerViewController languagePickerViewController]; + languagePickerViewController.selectedLanguage = [NSBundle mxk_language]; + languagePickerViewController.delegate = self; + [self pushViewController:languagePickerViewController]; + } + else if (row == USER_INTERFACE_THEME_INDEX) + { + [self showThemePicker]; + } + } + else if (section == SETTINGS_SECTION_USER_SETTINGS_INDEX && row == userSettingsThreePidsInformation) + { + NSIndexPath *discoveryIndexPath = [NSIndexPath indexPathForRow:0 inSection:SETTINGS_SECTION_DISCOVERY_INDEX]; + [tableView scrollToRowAtIndexPath:discoveryIndexPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; + } + else if (section == SETTINGS_SECTION_DISCOVERY_INDEX) + { + [self.settingsDiscoveryTableViewSection selectRow:indexPath.row]; + } + else if (section == SETTINGS_SECTION_IDENTITY_SERVER_INDEX) + { + switch (row) + { + case IDENTITY_SERVER_INDEX: + [self showIdentityServerSettingsScreen]; + break; + } + } + else if (section == SETTINGS_SECTION_IGNORED_USERS_INDEX) + { + MXSession* session = [AppDelegate theDelegate].mxSessions[0]; + + NSString *ignoredUserId; + if (indexPath.row < session.ignoredUsers.count) + { + ignoredUserId = session.ignoredUsers[indexPath.row]; + } + + if (ignoredUserId) + { + [currentAlert dismissViewControllerAnimated:NO completion:nil]; + + __weak typeof(self) weakSelf = self; + + currentAlert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:NSLocalizedStringFromTable(@"settings_unignore_user", @"Vector", nil), ignoredUserId] message:nil preferredStyle:UIAlertControllerStyleAlert]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"yes"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->currentAlert = nil; + + MXSession* session = [AppDelegate theDelegate].mxSessions[0]; + + // Remove the member from the ignored user list + [self startActivityIndicator]; + [session unIgnoreUsers:@[ignoredUserId] success:^{ + + [self stopActivityIndicator]; + + } failure:^(NSError *error) { + + [self stopActivityIndicator]; + + NSLog(@"[SettingsViewController] Unignore %@ failed", ignoredUserId); + + NSString *myUserId = session.myUser.userId; + [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; + + }]; + } + + }]]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"no"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->currentAlert = nil; + } + + }]]; + + [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCUnignoreAlert"]; + [self presentViewController:currentAlert animated:YES completion:nil]; + } + } + else if (section == SETTINGS_SECTION_OTHER_INDEX) + { + if (row == OTHER_COPYRIGHT_INDEX) + { + WebViewViewController *webViewViewController = [[WebViewViewController alloc] initWithURL:NSLocalizedStringFromTable(@"settings_copyright_url", @"Vector", nil)]; + + webViewViewController.title = NSLocalizedStringFromTable(@"settings_copyright", @"Vector", nil); + + [self pushViewController:webViewViewController]; + } + else if (row == OTHER_TERM_CONDITIONS_INDEX) + { + WebViewViewController *webViewViewController = [[WebViewViewController alloc] initWithURL:NSLocalizedStringFromTable(@"settings_term_conditions_url", @"Vector", nil)]; + + webViewViewController.title = NSLocalizedStringFromTable(@"settings_term_conditions", @"Vector", nil); + + [self pushViewController:webViewViewController]; + } + else if (row == OTHER_PRIVACY_INDEX) + { + WebViewViewController *webViewViewController = [[WebViewViewController alloc] initWithURL:NSLocalizedStringFromTable(@"settings_privacy_policy_url", @"Vector", nil)]; + + webViewViewController.title = NSLocalizedStringFromTable(@"settings_privacy_policy", @"Vector", nil); + + [self pushViewController:webViewViewController]; + } + else if (row == OTHER_THIRD_PARTY_INDEX) + { + NSString *htmlFile = [[NSBundle mainBundle] pathForResource:@"third_party_licenses" ofType:@"html" inDirectory:nil]; + + WebViewViewController *webViewViewController = [[WebViewViewController alloc] initWithLocalHTMLFile:htmlFile]; + + webViewViewController.title = NSLocalizedStringFromTable(@"settings_third_party_notices", @"Vector", nil); + + [self pushViewController:webViewViewController]; + } + } + else if (section == SETTINGS_SECTION_USER_SETTINGS_INDEX) + { + if (row == userSettingsProfilePictureIndex) + { + [self onProfileAvatarTap:nil]; + } + else if (row == userSettingsChangePasswordIndex) + { + [self displayPasswordAlert]; + } + else if (row == userSettingsNewEmailIndex) + { + if (!self.newEmailEditingEnabled) + { + // Enable the new email text field + self.newEmailEditingEnabled = YES; + } + else if (newEmailTextField) + { + [self onAddNewEmail:newEmailTextField]; + } + } + else if (row == userSettingsNewPhoneIndex) + { + if (!self.newPhoneEditingEnabled) + { + // Enable the new phone text field + self.newPhoneEditingEnabled = YES; + } + else if (newPhoneNumberCell.mxkTextField) + { + [self onAddNewPhone:newPhoneNumberCell.mxkTextField]; + } + } + } + else if (section == SETTINGS_SECTION_DEVICES_INDEX) + { + if (row > DEVICES_DESCRIPTION_INDEX) + { + NSUInteger deviceIndex = row - 1; + if (deviceIndex < devicesArray.count) + { + [self showDeviceDetails:devicesArray[deviceIndex]]; + } + } + } + else if (section == SETTINGS_SECTION_CONTACTS_INDEX) + { + if (row == localContactsPhoneBookCountryIndex) + { + CountryPickerViewController *countryPicker = [CountryPickerViewController countryPickerViewController]; + countryPicker.view.tag = SETTINGS_SECTION_CONTACTS_INDEX; + countryPicker.delegate = self; + countryPicker.showCountryCallingCode = YES; + [self pushViewController:countryPicker]; + } + } + + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + } +} + +#pragma mark - actions + + +- (void)onSignout:(id)sender +{ + self.signOutButton = (UIButton*)sender; + + MXKeyBackup *keyBackup = self.mainSession.crypto.backup; + + [self.signOutAlertPresenter presentFor:keyBackup.state + areThereKeysToBackup:keyBackup.hasKeysToBackup + from:self + sourceView:self.signOutButton + animated:YES]; +} + +- (void)onRemove3PID:(NSIndexPath*)path +{ + NSUInteger section = path.section; + NSUInteger row = path.row; + + if (section == SETTINGS_SECTION_USER_SETTINGS_INDEX) + { + NSString *address, *medium; + MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; + NSString *promptMsg; + + if (userSettingsEmailStartIndex <= row && row < userSettingsNewEmailIndex) + { + medium = kMX3PIDMediumEmail; + row = row - userSettingsEmailStartIndex; + NSArray *linkedEmails = account.linkedEmails; + if (row < linkedEmails.count) + { + address = linkedEmails[row]; + promptMsg = [NSString stringWithFormat:NSLocalizedStringFromTable(@"settings_remove_email_prompt_msg", @"Vector", nil), address]; + } + } + else if (userSettingsPhoneStartIndex <= row && row < userSettingsNewPhoneIndex) + { + medium = kMX3PIDMediumMSISDN; + row = row - userSettingsPhoneStartIndex; + NSArray *linkedPhones = account.linkedPhoneNumbers; + if (row < linkedPhones.count) + { + address = linkedPhones[row]; + NSString *e164 = [NSString stringWithFormat:@"+%@", address]; + NBPhoneNumber *phoneNb = [[NBPhoneNumberUtil sharedInstance] parse:e164 defaultRegion:nil error:nil]; + NSString *phoneMunber = [[NBPhoneNumberUtil sharedInstance] format:phoneNb numberFormat:NBEPhoneNumberFormatINTERNATIONAL error:nil]; + + promptMsg = [NSString stringWithFormat:NSLocalizedStringFromTable(@"settings_remove_phone_prompt_msg", @"Vector", nil), phoneMunber]; + } + } + + if (address && medium) + { + __weak typeof(self) weakSelf = self; + + if (currentAlert) + { + [currentAlert dismissViewControllerAnimated:NO completion:nil]; + currentAlert = nil; + } + + // Remove ? + currentAlert = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"settings_remove_prompt_title", @"Vector", nil) message:promptMsg preferredStyle:UIAlertControllerStyleAlert]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] + style:UIAlertActionStyleCancel + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->currentAlert = nil; + } + + }]]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"remove", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->currentAlert = nil; + + [self startActivityIndicator]; + + [self.mainSession.matrixRestClient remove3PID:address medium:medium success:^{ + + if (weakSelf) + { + typeof(self) self = weakSelf; + + [self stopActivityIndicator]; + + // Update linked 3pids + [self loadAccount3PIDs]; + } + + } failure:^(NSError *error) { + + NSLog(@"[SettingsViewController] Remove 3PID: %@ failed", address); + if (weakSelf) + { + typeof(self) self = weakSelf; + + [self stopActivityIndicator]; + + NSString *myUserId = self.mainSession.myUser.userId; // TODO: Hanlde multi-account + [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; + } + }]; + } + + }]]; + + [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCRemove3PIDAlert"]; + [self presentViewController:currentAlert animated:YES completion:nil]; + } + } +} + +- (void)togglePushNotifications:(id)sender +{ + // Check first whether the user allow notification from device settings + UIUserNotificationType currentUserNotificationTypes = UIApplication.sharedApplication.currentUserNotificationSettings.types; + if (currentUserNotificationTypes == UIUserNotificationTypeNone) + { + [currentAlert dismissViewControllerAnimated:NO completion:nil]; + + __weak typeof(self) weakSelf = self; + + NSString *appDisplayName = [[NSBundle mainBundle] infoDictionary][@"CFBundleDisplayName"]; + + currentAlert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:NSLocalizedStringFromTable(@"settings_on_denied_notification", @"Vector", nil), appDisplayName] message:nil preferredStyle:UIAlertControllerStyleAlert]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->currentAlert = nil; + } + + }]]; + + [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCPushNotificationsAlert"]; + [self presentViewController:currentAlert animated:YES completion:nil]; + + // Keep off the switch + ((UISwitch*)sender).on = NO; + } + else if ([MXKAccountManager sharedManager].activeAccounts.count) + { + [self startActivityIndicator]; + + MXKAccountManager *accountManager = [MXKAccountManager sharedManager]; + MXKAccount* account = accountManager.activeAccounts.firstObject; + + if (accountManager.pushDeviceToken) + { + [account enablePushKitNotifications:!account.isPushKitNotificationActive success:^{ + [self stopActivityIndicator]; + } failure:^(NSError *error) { + [self stopActivityIndicator]; + }]; + } + else + { + // Obtain device token when user has just enabled access to notifications from system settings + [[AppDelegate theDelegate] registerForRemoteNotificationsWithCompletion:^(NSError * error) { + if (error) + { + [(UISwitch *)sender setOn:NO animated:YES]; + [self stopActivityIndicator]; + } + else + { + [account enablePushKitNotifications:YES success:^{ + [self stopActivityIndicator]; + } failure:^(NSError *error) { + [self stopActivityIndicator]; + }]; + } + }]; + } + } +} + +- (void)toggleCallKit:(id)sender +{ + UISwitch *switchButton = (UISwitch*)sender; + [MXKAppSettings standardAppSettings].enableCallKit = switchButton.isOn; +} + +- (void)toggleStunServerFallback:(id)sender +{ + UISwitch *switchButton = (UISwitch*)sender; + RiotSettings.shared.allowStunServerFallback = switchButton.isOn; + + self.mainSession.callManager.fallbackSTUNServer = RiotSettings.shared.allowStunServerFallback ? RiotSettings.shared.stunServerFallback : nil; +} + +- (void)toggleAllowIntegrations:(id)sender +{ + UISwitch *switchButton = (UISwitch*)sender; + + MXSession *session = self.mainSession; + [self startActivityIndicator]; + + __block RiotSharedSettings *sharedSettings = [[RiotSharedSettings alloc] initWithSession:session]; + [sharedSettings setIntegrationProvisioningWithEnabled:switchButton.on success:^{ + sharedSettings = nil; + [self stopActivityIndicator]; + } failure:^(NSError * _Nullable error) { + sharedSettings = nil; + [switchButton setOn:!switchButton.on animated:YES]; + [self stopActivityIndicator]; + }]; +} + +- (void)toggleShowDecodedContent:(id)sender +{ + UISwitch *switchButton = (UISwitch*)sender; + RiotSettings.shared.showDecryptedContentInNotifications = switchButton.isOn; +} + +- (void)toggleLocalContactsSync:(id)sender +{ + UISwitch *switchButton = (UISwitch*)sender; + + if (switchButton.on) + { + [MXKContactManager requestUserConfirmationForLocalContactsSyncInViewController:self completionHandler:^(BOOL granted) { + + [MXKAppSettings standardAppSettings].syncLocalContacts = granted; + + [self.tableView reloadData]; + }]; + } + else + { + [MXKAppSettings standardAppSettings].syncLocalContacts = NO; + + [self.tableView reloadData]; + } +} + +- (void)toggleSendCrashReport:(id)sender +{ + BOOL enable = RiotSettings.shared.enableCrashReport; + if (enable) + { + NSLog(@"[SettingsViewController] disable automatic crash report and analytics sending"); + + RiotSettings.shared.enableCrashReport = NO; + + [[Analytics sharedInstance] stop]; + + // Remove potential crash file. + [MXLogger deleteCrashLog]; + } + else + { + NSLog(@"[SettingsViewController] enable automatic crash report and analytics sending"); + + RiotSettings.shared.enableCrashReport = YES; + + [[Analytics sharedInstance] start]; + } +} + +- (void)toggleEnableRageShake:(id)sender +{ + if (sender && [sender isKindOfClass:UISwitch.class]) + { + UISwitch *switchButton = (UISwitch*)sender; + + RiotSettings.shared.enableRageShake = switchButton.isOn; + + [self.tableView reloadData]; + } +} + +- (void)toggleSyncWithLazyLoadOfRoomMembers:(id)sender +{ + if (sender && [sender isKindOfClass:UISwitch.class]) + { + UISwitch *switchButton = (UISwitch*)sender; + + if (!switchButton.isOn) + { + // Disable LL and reload + [MXKAppSettings standardAppSettings].syncWithLazyLoadOfRoomMembers = NO; + [self launchClearCache]; + } + else + { + switchButton.enabled = NO; + [self startActivityIndicator]; + + // Check the user homeserver supports lazy-loading + MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; + + MXWeakify(self); + [account supportLazyLoadOfRoomMembers:^(BOOL supportLazyLoadOfRoomMembers) { + MXStrongifyAndReturnIfNil(self); + + if (supportLazyLoadOfRoomMembers) + { + // Lazy-loading is fully supported, enable it + [MXKAppSettings standardAppSettings].syncWithLazyLoadOfRoomMembers = YES; + [self launchClearCache]; + } + else + { + [switchButton setOn:NO animated:YES]; + switchButton.enabled = YES; + [self stopActivityIndicator]; + + // No support of lazy-loading, do not engage it and warn the user + [self->currentAlert dismissViewControllerAnimated:NO completion:nil]; + + self->currentAlert = [UIAlertController alertControllerWithTitle:nil + message:NSLocalizedStringFromTable(@"settings_labs_room_members_lazy_loading_error_message", @"Vector", nil) + preferredStyle:UIAlertControllerStyleAlert]; + + MXWeakify(self); + [self->currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + self->currentAlert = nil; + }]]; + + [self->currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCNoHSSupportOfLazyLoading"]; + [self presentViewController:self->currentAlert animated:YES completion:nil]; + } + }]; + } + } +} + + +- (void)toggleJitsiForConference:(id)sender +{ + if (sender && [sender isKindOfClass:UISwitch.class]) + { + UISwitch *switchButton = (UISwitch*)sender; + + RiotSettings.shared.createConferenceCallsWithJitsi = switchButton.isOn; + + [self.tableView reloadData]; + } +} + +- (void)toggleLabsCrossSigning:(id)sender +{ + UISwitch *switchButton = (UISwitch*)sender; + + RiotSettings.shared.enableCrossSigning = switchButton.isOn; +} + +- (void)toggleBlacklistUnverifiedDevices:(id)sender +{ + UISwitch *switchButton = (UISwitch*)sender; + + MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; + account.mxSession.crypto.globalBlacklistUnverifiedDevices = switchButton.on; + + [self.tableView reloadData]; +} + +- (void)togglePinRoomsWithMissedNotif:(id)sender +{ + UISwitch *switchButton = (UISwitch*)sender; + + RiotSettings.shared.pinRoomsWithMissedNotificationsOnHome = switchButton.on; +} + +- (void)togglePinRoomsWithUnread:(id)sender +{ + UISwitch *switchButton = (UISwitch*)sender; + + RiotSettings.shared.pinRoomsWithUnreadMessagesOnHome = switchButton.on; +} + +- (void)toggleCommunityFlair:(id)sender +{ + UISwitch *switchButton = (UISwitch*)sender; + + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:switchButton.tag inSection:groupsDataSource.joinedGroupsSection]; + id groupCellData = [groupsDataSource cellDataAtIndex:indexPath]; + MXGroup *group = groupCellData.group; + + if (group) + { + [self startActivityIndicator]; + + __weak typeof(self) weakSelf = self; + + [self.mainSession updateGroupPublicity:group isPublicised:switchButton.on success:^{ + + if (weakSelf) + { + typeof(self) self = weakSelf; + [self stopActivityIndicator]; + } + + } failure:^(NSError *error) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + [self stopActivityIndicator]; + + // Come back to previous state button + [switchButton setOn:!switchButton.isOn animated:YES]; + + // Notify user + [[AppDelegate theDelegate] showErrorAsAlert:error]; + } + }]; + } +} + +- (void)markAllAsRead:(id)sender +{ + // Feedback: disable button and run activity indicator + UIButton *button = (UIButton*)sender; + button.enabled = NO; + [self startActivityIndicator]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + + [[AppDelegate theDelegate] markAllMessagesAsRead]; + + [self stopActivityIndicator]; + button.enabled = YES; + + }); +} + +- (void)clearCache:(id)sender +{ + // Feedback: disable button and run activity indicator + UIButton *button = (UIButton*)sender; + button.enabled = NO; + + [self launchClearCache]; +} + +- (void)launchClearCache +{ + [self startActivityIndicator]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + + [[AppDelegate theDelegate] reloadMatrixSessions:YES]; + + }); +} + +- (void)reportBug:(id)sender +{ + BugReportViewController *bugReportViewController = [BugReportViewController bugReportViewController]; + [bugReportViewController showInViewController:self]; +} + +- (void)selectPhoneNumberCountry:(id)sender +{ + newPhoneNumberCountryPicker = [CountryPickerViewController countryPickerViewController]; + newPhoneNumberCountryPicker.view.tag = SETTINGS_SECTION_USER_SETTINGS_INDEX; + newPhoneNumberCountryPicker.delegate = self; + newPhoneNumberCountryPicker.showCountryCallingCode = YES; + [self pushViewController:newPhoneNumberCountryPicker]; +} + +//- (void)onRuleUpdate:(id)sender +//{ +// MXPushRule* pushRule = nil; +// MXSession* session = [[AppDelegate theDelegate].mxSessions objectAtIndex:0]; +// +// NSInteger row = ((UIView*)sender).tag; +// +// if (row == NOTIFICATION_SETTINGS_CONTAINING_MY_DISPLAY_NAME_INDEX) +// { +// pushRule = [session.notificationCenter ruleById:kMXNotificationCenterContainDisplayNameRuleID]; +// } +// else if (row == NOTIFICATION_SETTINGS_CONTAINING_MY_USER_NAME_INDEX) +// { +// pushRule = [session.notificationCenter ruleById:kMXNotificationCenterContainUserNameRuleID]; +// } +// else if (row == NOTIFICATION_SETTINGS_SENT_TO_ME_INDEX) +// { +// pushRule = [session.notificationCenter ruleById:kMXNotificationCenterOneToOneRoomRuleID]; +// } +// else if (row == NOTIFICATION_SETTINGS_INVITED_TO_ROOM_INDEX) +// { +// pushRule = [session.notificationCenter ruleById:kMXNotificationCenterInviteMeRuleID]; +// } +// else if (row == NOTIFICATION_SETTINGS_PEOPLE_LEAVE_JOIN_INDEX) +// { +// pushRule = [session.notificationCenter ruleById:kMXNotificationCenterMemberEventRuleID]; +// } +// else if (row == NOTIFICATION_SETTINGS_CALL_INVITATION_INDEX) +// { +// pushRule = [session.notificationCenter ruleById:kMXNotificationCenterCallRuleID]; +// } +// +// if (pushRule) +// { +// // toggle the rule +// [session.notificationCenter enableRule:pushRule isEnabled:!pushRule.enabled]; +// } +//} + + +- (void)onSave:(id)sender +{ + // sanity check + if ([MXKAccountManager sharedManager].activeAccounts.count == 0) + { + return; + } + + self.navigationItem.rightBarButtonItem.enabled = NO; + [self startActivityIndicator]; + isSavingInProgress = YES; + __weak typeof(self) weakSelf = self; + + MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; + MXMyUser* myUser = account.mxSession.myUser; + + if (newDisplayName && ![myUser.displayname isEqualToString:newDisplayName]) + { + // Save display name + [account setUserDisplayName:newDisplayName success:^{ + + if (weakSelf) + { + // Update the current displayname + typeof(self) self = weakSelf; + self->newDisplayName = nil; + + // Go to the next change saving step + [self onSave:nil]; + } + + } failure:^(NSError *error) { + + NSLog(@"[SettingsViewController] Failed to set displayName"); + + if (weakSelf) + { + typeof(self) self = weakSelf; + [self handleErrorDuringProfileChangeSaving:error]; + } + + }]; + + return; + } + + if (newAvatarImage) + { + // Retrieve the current picture and make sure its orientation is up + UIImage *updatedPicture = [MXKTools forceImageOrientationUp:newAvatarImage]; + + // Upload picture + MXMediaLoader *uploader = [MXMediaManager prepareUploaderWithMatrixSession:account.mxSession initialRange:0 andRange:1.0]; + + [uploader uploadData:UIImageJPEGRepresentation(updatedPicture, 0.5) filename:nil mimeType:@"image/jpeg" success:^(NSString *url) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + + // Store uploaded picture url and trigger picture saving + self->uploadedAvatarURL = url; + self->newAvatarImage = nil; + [self onSave:nil]; + } + + + } failure:^(NSError *error) { + + NSLog(@"[SettingsViewController] Failed to upload image"); + + if (weakSelf) + { + typeof(self) self = weakSelf; + [self handleErrorDuringProfileChangeSaving:error]; + } + + }]; + + return; + } + else if (uploadedAvatarURL) + { + [account setUserAvatarUrl:uploadedAvatarURL + success:^{ + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->uploadedAvatarURL = nil; + [self onSave:nil]; + } + + } + failure:^(NSError *error) { + + NSLog(@"[SettingsViewController] Failed to set avatar url"); + + if (weakSelf) + { + typeof(self) self = weakSelf; + [self handleErrorDuringProfileChangeSaving:error]; + } + + }]; + + return; + } + + // Backup is complete + isSavingInProgress = NO; + [self stopActivityIndicator]; + + // Check whether destroy has been called durign saving + if (onReadyToDestroyHandler) + { + // Ready to destroy + onReadyToDestroyHandler(); + onReadyToDestroyHandler = nil; + } + else + { + [self.tableView reloadData]; + } +} + +- (void)handleErrorDuringProfileChangeSaving:(NSError*)error +{ + // Sanity check: retrieve the current root view controller + UIViewController *rootViewController = [AppDelegate theDelegate].window.rootViewController; + if (rootViewController) + { + __weak typeof(self) weakSelf = self; + + // Alert user + NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey]; + if (!title) + { + title = [NSBundle mxk_localizedStringForKey:@"settings_fail_to_update_profile"]; + } + NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey]; + + [currentAlert dismissViewControllerAnimated:NO completion:nil]; + + currentAlert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + + self->currentAlert = nil; + + // Reset the updated displayname + self->newDisplayName = nil; + + // Discard picture change + self->uploadedAvatarURL = nil; + self->newAvatarImage = nil; + + // Loop to end saving + [self onSave:nil]; + } + + }]]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"retry"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + + self->currentAlert = nil; + + // Loop to retry saving + [self onSave:nil]; + } + + }]]; + + [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCSaveChangesFailedAlert"]; + [rootViewController presentViewController:currentAlert animated:YES completion:nil]; + } +} + +- (IBAction)onAddNewEmail:(id)sender +{ + // Ignore empty field + if (!newEmailTextField.text.length) + { + // Reset new email adding + self.newEmailEditingEnabled = NO; + return; + } + + // Email check + if (![MXTools isEmailAddress:newEmailTextField.text]) + { + __weak typeof(self) weakSelf = self; + + [currentAlert dismissViewControllerAnimated:NO completion:nil]; + + currentAlert = [UIAlertController alertControllerWithTitle:[NSBundle mxk_localizedStringForKey:@"account_error_email_wrong_title"] message:[NSBundle mxk_localizedStringForKey:@"account_error_email_wrong_description"] preferredStyle:UIAlertControllerStyleAlert]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + + self->currentAlert = nil; + } + + }]]; + + [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCAddEmailAlert"]; + [self presentViewController:currentAlert animated:YES completion:nil]; + + return; + } + + // Dismiss the keyboard + [newEmailTextField resignFirstResponder]; + + MXSession* session = [AppDelegate theDelegate].mxSessions[0]; + + [self checkAuthenticationFlowForAdding:kMX3PIDMediumEmail withSession:session onComplete:^(NSString *password) { + + [self startActivityIndicator]; + + __block MX3PidAddSession *thirdPidAddSession; + thirdPidAddSession = [session.threePidAddManager startAddEmailSessionWithEmail:self->newEmailTextField.text nextLink:nil success:^{ + + [self showValidationEmailDialogWithMessage:[NSBundle mxk_localizedStringForKey:@"account_email_validation_message"] + for3PidAddSession:thirdPidAddSession + threePidAddManager:session.threePidAddManager + password:password]; + + } failure:^(NSError * _Nonnull error) { + + [self stopActivityIndicator]; + + NSLog(@"[SettingsViewController] Failed to request email token"); + + // Translate the potential MX error. + MXError *mxError = [[MXError alloc] initWithNSError:error]; + if (mxError + && ([mxError.errcode isEqualToString:kMXErrCodeStringThreePIDInUse] + || [mxError.errcode isEqualToString:kMXErrCodeStringServerNotTrusted])) + { + NSMutableDictionary *userInfo; + if (error.userInfo) + { + userInfo = [NSMutableDictionary dictionaryWithDictionary:error.userInfo]; + } + else + { + userInfo = [NSMutableDictionary dictionary]; + } + + userInfo[NSLocalizedFailureReasonErrorKey] = nil; + + if ([mxError.errcode isEqualToString:kMXErrCodeStringThreePIDInUse]) + { + userInfo[NSLocalizedDescriptionKey] = NSLocalizedStringFromTable(@"auth_email_in_use", @"Vector", nil); + userInfo[@"error"] = NSLocalizedStringFromTable(@"auth_email_in_use", @"Vector", nil); + } + else + { + userInfo[NSLocalizedDescriptionKey] = NSLocalizedStringFromTable(@"auth_untrusted_id_server", @"Vector", nil); + userInfo[@"error"] = NSLocalizedStringFromTable(@"auth_untrusted_id_server", @"Vector", nil); + } + + error = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo]; + } + else if ([error.domain isEqualToString:MX3PidAddManagerErrorDomain] + && error.code == MX3PidAddManagerErrorDomainIdentityServerRequired) + { + error = [NSError errorWithDomain:error.domain + code:error.code + userInfo:@{ + NSLocalizedDescriptionKey: [NSBundle mxk_localizedStringForKey:@"auth_email_is_required"] + }]; + } + + // Notify user + NSString *myUserId = session.myUser.userId; // TODO: Hanlde multi-account + [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; + + }]; + }]; +} + +- (IBAction)onAddNewPhone:(id)sender +{ + // Ignore empty field + if (!newPhoneNumberCell.mxkTextField.text.length) + { + // Disable the new phone edition if the text field is empty + self.newPhoneEditingEnabled = NO; + return; + } + + // Phone check + if (![[NBPhoneNumberUtil sharedInstance] isValidNumber:newPhoneNumber]) + { + [currentAlert dismissViewControllerAnimated:NO completion:nil]; + __weak typeof(self) weakSelf = self; + + currentAlert = [UIAlertController alertControllerWithTitle:[NSBundle mxk_localizedStringForKey:@"account_error_msisdn_wrong_title"] message:[NSBundle mxk_localizedStringForKey:@"account_error_msisdn_wrong_description"] preferredStyle:UIAlertControllerStyleAlert]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->currentAlert = nil; + } + + }]]; + + [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCAddMsisdnAlert"]; + [self presentViewController:currentAlert animated:YES completion:nil]; + + return; + } + + // Dismiss the keyboard + [newPhoneNumberCell.mxkTextField resignFirstResponder]; + + MXSession* session = [AppDelegate theDelegate].mxSessions[0]; + + NSString *e164 = [[NBPhoneNumberUtil sharedInstance] format:newPhoneNumber numberFormat:NBEPhoneNumberFormatE164 error:nil]; + NSString *msisdn; + if ([e164 hasPrefix:@"+"]) + { + msisdn = e164; + } + else if ([e164 hasPrefix:@"00"]) + { + msisdn = [NSString stringWithFormat:@"+%@", [e164 substringFromIndex:2]]; + } + + [self checkAuthenticationFlowForAdding:kMX3PIDMediumMSISDN withSession:session onComplete:^(NSString *password) { + [self startActivityIndicator]; + + __block MX3PidAddSession *new3Pid; + new3Pid = [session.threePidAddManager startAddPhoneNumberSessionWithPhoneNumber:msisdn countryCode:nil success:^{ + + [self showValidationMsisdnDialogWithMessage:[NSBundle mxk_localizedStringForKey:@"account_msisdn_validation_message"] for3PidAddSession:new3Pid threePidAddManager:session.threePidAddManager password:password]; + + } failure:^(NSError *error) { + + [self stopActivityIndicator]; + + NSLog(@"[SettingsViewController] Failed to request msisdn token"); + + // Translate the potential MX error. + MXError *mxError = [[MXError alloc] initWithNSError:error]; + if (mxError + && ([mxError.errcode isEqualToString:kMXErrCodeStringThreePIDInUse] + || [mxError.errcode isEqualToString:kMXErrCodeStringServerNotTrusted])) + { + NSMutableDictionary *userInfo; + if (error.userInfo) + { + userInfo = [NSMutableDictionary dictionaryWithDictionary:error.userInfo]; + } + else + { + userInfo = [NSMutableDictionary dictionary]; + } + + userInfo[NSLocalizedFailureReasonErrorKey] = nil; + + if ([mxError.errcode isEqualToString:kMXErrCodeStringThreePIDInUse]) + { + userInfo[NSLocalizedDescriptionKey] = NSLocalizedStringFromTable(@"auth_phone_in_use", @"Vector", nil); + userInfo[@"error"] = NSLocalizedStringFromTable(@"auth_phone_in_use", @"Vector", nil); + } + else + { + userInfo[NSLocalizedDescriptionKey] = NSLocalizedStringFromTable(@"auth_untrusted_id_server", @"Vector", nil); + userInfo[@"error"] = NSLocalizedStringFromTable(@"auth_untrusted_id_server", @"Vector", nil); + } + + error = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo]; + } + else if ([error.domain isEqualToString:MX3PidAddManagerErrorDomain] + && error.code == MX3PidAddManagerErrorDomainIdentityServerRequired) + { + error = [NSError errorWithDomain:error.domain + code:error.code + userInfo:@{ + NSLocalizedDescriptionKey: [NSBundle mxk_localizedStringForKey:@"auth_phone_is_required"] + }]; + } + + // Notify user + NSString *myUserId = session.myUser.userId; + [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; + }]; + }]; +} + +- (void)updateSaveButtonStatus +{ + if ([AppDelegate theDelegate].mxSessions.count > 0) + { + MXSession* session = [AppDelegate theDelegate].mxSessions[0]; + MXMyUser* myUser = session.myUser; + + BOOL saveButtonEnabled = (nil != newAvatarImage); + + if (!saveButtonEnabled) + { + if (newDisplayName) + { + saveButtonEnabled = ![myUser.displayname isEqualToString:newDisplayName]; + } + } + + self.navigationItem.rightBarButtonItem.enabled = saveButtonEnabled; + } +} + +- (void)onProfileAvatarTap:(UITapGestureRecognizer *)recognizer +{ + SingleImagePickerPresenter *singleImagePickerPresenter = [[SingleImagePickerPresenter alloc] initWithSession:self.mainSession]; + singleImagePickerPresenter.delegate = self; + + + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:userSettingsProfilePictureIndex inSection:SETTINGS_SECTION_USER_SETTINGS_INDEX]; + UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; + + UIView *sourceView = cell; + + [singleImagePickerPresenter presentFrom:self sourceView:sourceView sourceRect:sourceView.bounds animated:YES]; + + self.imagePickerPresenter = singleImagePickerPresenter; +} + +- (void)exportEncryptionKeys:(UITapGestureRecognizer *)recognizer +{ + [currentAlert dismissViewControllerAnimated:NO completion:nil]; + + exportView = [[MXKEncryptionKeysExportView alloc] initWithMatrixSession:self.mainSession]; + currentAlert = exportView.alertController; + + // Use a temporary file for the export + keyExportsFile = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"riot-keys.txt"]]; + + // Make sure the file is empty + [self deleteKeyExportFile]; + + // Show the export dialog + __weak typeof(self) weakSelf = self; + [exportView showInViewController:self toExportKeysToFile:keyExportsFile onComplete:^(BOOL success) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->currentAlert = nil; + self->exportView = nil; + + if (success) + { + // Let another app handling this file + self->documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:keyExportsFile]; + [self->documentInteractionController setDelegate:self]; + + if ([self->documentInteractionController presentOptionsMenuFromRect:self.view.bounds inView:self.view animated:YES]) + { + // We want to delete the temp keys file after it has been processed by the other app. + // We use [UIDocumentInteractionControllerDelegate didEndSendingToApplication] for that + // but it is not reliable for all cases (see http://stackoverflow.com/a/21867096). + // So, arm a timer to auto delete the file after 10mins. + keyExportsFileDeletionTimer = [NSTimer scheduledTimerWithTimeInterval:600 target:self selector:@selector(deleteKeyExportFile) userInfo:self repeats:NO]; + } + else + { + self->documentInteractionController = nil; + [self deleteKeyExportFile]; + } + } + } + }]; +} + +- (void)deleteKeyExportFile +{ + // Cancel the deletion timer if it is still here + if (keyExportsFileDeletionTimer) + { + [keyExportsFileDeletionTimer invalidate]; + keyExportsFileDeletionTimer = nil; + } + + // And delete the file + if (keyExportsFile && [[NSFileManager defaultManager] fileExistsAtPath:keyExportsFile.path]) + { + [[NSFileManager defaultManager] removeItemAtPath:keyExportsFile.path error:nil]; + } +} + +- (void)showThemePicker +{ + __weak typeof(self) weakSelf = self; + + __block UIAlertAction *autoAction, *lightAction, *darkAction, *blackAction; + NSString *themePickerMessage; + + void (^actionBlock)(UIAlertAction *action) = ^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + + NSString *newTheme; + if (action == autoAction) + { + newTheme = @"auto"; + } + else if (action == lightAction) + { + newTheme = @"light"; + } + else if (action == darkAction) + { + newTheme = @"dark"; + } + else if (action == blackAction) + { + newTheme = @"black"; + } + + NSString *theme = RiotSettings.shared.userInterfaceTheme; + if (newTheme && ![newTheme isEqualToString:theme]) + { + // Clear fake Riot Avatars based on the previous theme. + [AvatarGenerator clear]; + + // The user wants to select this theme + RiotSettings.shared.userInterfaceTheme = newTheme; + ThemeService.shared.themeId = newTheme; + + [self.tableView reloadData]; + } + } + }; + + if (@available(iOS 11.0, *)) + { + // Show "auto" only from iOS 11 + autoAction = [UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"settings_ui_theme_auto", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:actionBlock]; + + // Explain what is "auto" + themePickerMessage = NSLocalizedStringFromTable(@"settings_ui_theme_picker_message", @"Vector", nil); + } + + lightAction = [UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"settings_ui_theme_light", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:actionBlock]; + + darkAction = [UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"settings_ui_theme_dark", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:actionBlock]; + blackAction = [UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"settings_ui_theme_black", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:actionBlock]; + + + UIAlertController *themePicker = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"settings_ui_theme_picker_title", @"Vector", nil) + message:themePickerMessage + preferredStyle:UIAlertControllerStyleActionSheet]; + + if (autoAction) + { + [themePicker addAction:autoAction]; + } + [themePicker addAction:lightAction]; + [themePicker addAction:darkAction]; + [themePicker addAction:blackAction]; + + // Cancel button + [themePicker addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] + style:UIAlertActionStyleCancel + handler:nil]]; + + UIView *fromCell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:USER_INTERFACE_THEME_INDEX inSection:SETTINGS_SECTION_USER_INTERFACE_INDEX]]; + [themePicker popoverPresentationController].sourceView = fromCell; + [themePicker popoverPresentationController].sourceRect = fromCell.bounds; + + [self presentViewController:themePicker animated:YES completion:nil]; +} + +- (void)deactivateAccountAction +{ + DeactivateAccountViewController *deactivateAccountViewController = [DeactivateAccountViewController instantiateWithMatrixSession:self.mainSession]; + + UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:deactivateAccountViewController]; + navigationController.modalPresentationStyle = UIModalPresentationFormSheet; + + [self presentViewController:navigationController animated:YES completion:nil]; + + deactivateAccountViewController.delegate = self; + + self.deactivateAccountViewController = deactivateAccountViewController; +} + +#pragma mark - TextField listener + +- (IBAction)textFieldDidChange:(id)sender +{ + UITextField* textField = (UITextField*)sender; + + if (textField.tag == userSettingsDisplayNameIndex) + { + // Remove white space from both ends + newDisplayName = [textField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + [self updateSaveButtonStatus]; + } + else if (textField.tag == userSettingsNewPhoneIndex) + { + newPhoneNumber = [[NBPhoneNumberUtil sharedInstance] parse:textField.text defaultRegion:newPhoneNumberCell.isoCountryCode error:nil]; + + [self formatNewPhoneNumber]; + } +} + +- (IBAction)textFieldDidEnd:(id)sender +{ + UITextField* textField = (UITextField*)sender; + + // Disable the new email edition if the user leaves the text field empty + if (textField.tag == userSettingsNewEmailIndex && textField.text.length == 0 && !keepNewEmailEditing) + { + self.newEmailEditingEnabled = NO; + } + else if (textField.tag == userSettingsNewPhoneIndex && textField.text.length == 0 && !keepNewPhoneNumberEditing && !newPhoneNumberCountryPicker) + { + // Disable the new phone edition if the user leaves the text field empty + self.newPhoneEditingEnabled = NO; + } +} + +#pragma mark - UITextField delegate + +- (void)textFieldDidBeginEditing:(UITextField *)textField +{ + if (textField.tag == userSettingsDisplayNameIndex) + { + textField.textAlignment = NSTextAlignmentLeft; + } +} +- (void)textFieldDidEndEditing:(UITextField *)textField +{ + if (textField.tag == userSettingsDisplayNameIndex) + { + textField.textAlignment = NSTextAlignmentRight; + } +} + +- (BOOL)textFieldShouldReturn:(UITextField *)textField +{ + if (textField.tag == userSettingsDisplayNameIndex) + { + [textField resignFirstResponder]; + } + else if (textField.tag == userSettingsNewEmailIndex) + { + [self onAddNewEmail:textField]; + } + + return YES; +} + +#pragma password update management + +- (IBAction)passwordTextFieldDidChange:(id)sender +{ + savePasswordAction.enabled = (currentPasswordTextField.text.length > 0) && (newPasswordTextField1.text.length > 2) && [newPasswordTextField1.text isEqualToString:newPasswordTextField2.text]; +} + +- (void)displayPasswordAlert +{ + __weak typeof(self) weakSelf = self; + [resetPwdAlertController dismissViewControllerAnimated:NO completion:nil]; + + resetPwdAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"settings_change_password", @"Vector", nil) message:nil preferredStyle:UIAlertControllerStyleAlert]; + resetPwdAlertController.accessibilityLabel=@"ChangePasswordAlertController"; + savePasswordAction = [UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"save", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + + self->resetPwdAlertController = nil; + + if ([MXKAccountManager sharedManager].activeAccounts.count > 0) + { + [self startActivityIndicator]; + self->isResetPwdInProgress = YES; + + MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; + + [account changePassword:currentPasswordTextField.text with:newPasswordTextField1.text success:^{ + + if (weakSelf) + { + typeof(self) self = weakSelf; + + self->isResetPwdInProgress = NO; + [self stopActivityIndicator]; + + // Display a successful message only if the settings screen is still visible (destroy is not called yet) + if (!self->onReadyToDestroyHandler) + { + [self->currentAlert dismissViewControllerAnimated:NO completion:nil]; + + self->currentAlert = [UIAlertController alertControllerWithTitle:nil message:NSLocalizedStringFromTable(@"settings_password_updated", @"Vector", nil) preferredStyle:UIAlertControllerStyleAlert]; + + [self->currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->currentAlert = nil; + + // Check whether destroy has been called durign pwd change + if (self->onReadyToDestroyHandler) + { + // Ready to destroy + self->onReadyToDestroyHandler(); + self->onReadyToDestroyHandler = nil; + } + } + + }]]; + + [self->currentAlert mxk_setAccessibilityIdentifier:@"SettingsVCOnPasswordUpdatedAlert"]; + [self presentViewController:self->currentAlert animated:YES completion:nil]; + } + else + { + // Ready to destroy + self->onReadyToDestroyHandler(); + self->onReadyToDestroyHandler = nil; + } + } + + } failure:^(NSError *error) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + + self->isResetPwdInProgress = NO; + [self stopActivityIndicator]; + + // Display a failure message on the current screen + UIViewController *rootViewController = [AppDelegate theDelegate].window.rootViewController; + if (rootViewController) + { + [self->currentAlert dismissViewControllerAnimated:NO completion:nil]; + + self->currentAlert = [UIAlertController alertControllerWithTitle:nil message:NSLocalizedStringFromTable(@"settings_fail_to_update_password", @"Vector", nil) preferredStyle:UIAlertControllerStyleAlert]; + + [self->currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + + self->currentAlert = nil; + + // Check whether destroy has been called durign pwd change + if (self->onReadyToDestroyHandler) + { + // Ready to destroy + self->onReadyToDestroyHandler(); + self->onReadyToDestroyHandler = nil; + } + } + + }]]; + + [self->currentAlert mxk_setAccessibilityIdentifier:@"SettingsVCPasswordChangeFailedAlert"]; + [rootViewController presentViewController:self->currentAlert animated:YES completion:nil]; + } + } + + }]; + } + } + + }]; + + // disable by default + // check if the textfields have the right value + savePasswordAction.enabled = NO; + + UIAlertAction* cancel = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + + self->resetPwdAlertController = nil; + } + + }]; + + [resetPwdAlertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + + self->currentPasswordTextField = textField; + self->currentPasswordTextField.placeholder = NSLocalizedStringFromTable(@"settings_old_password", @"Vector", nil); + self->currentPasswordTextField.secureTextEntry = YES; + [self->currentPasswordTextField addTarget:self action:@selector(passwordTextFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; + } + + }]; + + [resetPwdAlertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + + self->newPasswordTextField1 = textField; + self->newPasswordTextField1.placeholder = NSLocalizedStringFromTable(@"settings_new_password", @"Vector", nil); + self->newPasswordTextField1.secureTextEntry = YES; + [self->newPasswordTextField1 addTarget:self action:@selector(passwordTextFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; + } + + }]; + + [resetPwdAlertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + + self->newPasswordTextField2 = textField; + self->newPasswordTextField2.placeholder = NSLocalizedStringFromTable(@"settings_confirm_password", @"Vector", nil); + self->newPasswordTextField2.secureTextEntry = YES; + [self->newPasswordTextField2 addTarget:self action:@selector(passwordTextFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; + } + }]; + + + [resetPwdAlertController addAction:cancel]; + [resetPwdAlertController addAction:savePasswordAction]; + [self presentViewController:resetPwdAlertController animated:YES completion:nil]; +} + +#pragma mark - UIDocumentInteractionControllerDelegate + +- (void)documentInteractionController:(UIDocumentInteractionController *)controller didEndSendingToApplication:(NSString *)application +{ + // If iOS wants to call this method, this is the right time to remove the file + [self deleteKeyExportFile]; +} + +- (void)documentInteractionControllerDidDismissOptionsMenu:(UIDocumentInteractionController *)controller +{ + documentInteractionController = nil; +} + +#pragma mark - MXKCountryPickerViewControllerDelegate + +- (void)countryPickerViewController:(MXKCountryPickerViewController *)countryPickerViewController didSelectCountry:(NSString *)isoCountryCode +{ + if (countryPickerViewController.view.tag == SETTINGS_SECTION_CONTACTS_INDEX) + { + [MXKAppSettings standardAppSettings].phonebookCountryCode = isoCountryCode; + } + else if (countryPickerViewController.view.tag == SETTINGS_SECTION_USER_SETTINGS_INDEX) + { + if (newPhoneNumberCell) + { + newPhoneNumberCell.isoCountryCode = isoCountryCode; + + newPhoneNumber = [[NBPhoneNumberUtil sharedInstance] parse:newPhoneNumberCell.mxkTextField.text defaultRegion:isoCountryCode error:nil]; + [self formatNewPhoneNumber]; + } + } + + [countryPickerViewController withdrawViewControllerAnimated:YES completion:nil]; +} + +#pragma mark - MXKCountryPickerViewControllerDelegate + +- (void)languagePickerViewController:(MXKLanguagePickerViewController *)languagePickerViewController didSelectLangugage:(NSString *)language +{ + [languagePickerViewController withdrawViewControllerAnimated:YES completion:nil]; + + if (![language isEqualToString:[NSBundle mxk_language]] + || (language == nil && [NSBundle mxk_language])) + { + [NSBundle mxk_setLanguage:language]; + + // Store user settings + NSUserDefaults *sharedUserDefaults = [MXKAppSettings standardAppSettings].sharedUserDefaults; + [sharedUserDefaults setObject:language forKey:@"appLanguage"]; + + // Do a reload in order to recompute strings in the new language + // Note that "reloadMatrixSessions:NO" will reset room summaries + [self startActivityIndicator]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + + [[AppDelegate theDelegate] reloadMatrixSessions:NO]; + }); + } +} + +#pragma mark - MXKDataSourceDelegate + +- (Class)cellViewClassForCellData:(MXKCellData*)cellData +{ + // Return the class used to display a group with a toogle button + return GroupTableViewCellWithSwitch.class; +} + +- (NSString *)cellReuseIdentifierForCellData:(MXKCellData*)cellData +{ + return GroupTableViewCellWithSwitch.defaultReuseIdentifier; +} + +- (void)dataSource:(MXKDataSource *)dataSource didCellChange:(id)changes +{ + // Group data has been updated. Do a simple full reload + [self refreshSettings]; +} + +#pragma mark - DeactivateAccountViewControllerDelegate + +- (void)deactivateAccountViewControllerDidDeactivateWithSuccess:(DeactivateAccountViewController *)deactivateAccountViewController +{ + NSLog(@"[SettingsViewController] Deactivate account with success"); + + [[AppDelegate theDelegate] logoutSendingRequestServer:NO completion:^(BOOL isLoggedOut) { + NSLog(@"[SettingsViewController] Complete clear user data after account deactivation"); + }]; +} + +- (void)deactivateAccountViewControllerDidCancel:(DeactivateAccountViewController *)deactivateAccountViewController +{ + [deactivateAccountViewController dismissViewControllerAnimated:YES completion:nil]; +} + +#pragma mark - SettingsKeyBackupTableViewSectionDelegate + +- (void)settingsKeyBackupTableViewSectionDidUpdate:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection +{ + [self.tableView reloadData]; +} + +- (MXKTableViewCellWithTextView *)settingsKeyBackupTableViewSection:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection textCellForRow:(NSInteger)textCellForRow +{ + return [self textViewCellForTableView:self.tableView atIndexPath:[NSIndexPath indexPathForRow:textCellForRow inSection:SETTINGS_SECTION_KEYBACKUP_INDEX]]; +} + +- (MXKTableViewCellWithButton *)settingsKeyBackupTableViewSection:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection buttonCellForRow:(NSInteger)buttonCellForRow +{ + MXKTableViewCellWithButton *cell = [self.tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; + + if (!cell) + { + cell = [[MXKTableViewCellWithButton alloc] init]; + } + else + { + // Fix https://github.com/vector-im/riot-ios/issues/1354 + cell.mxkButton.titleLabel.text = nil; + } + + cell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; + [cell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; + + return cell; +} + +- (void)settingsKeyBackupTableViewSectionShowKeyBackupSetup:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection +{ + [self showKeyBackupSetupFromSignOutFlow:NO]; +} + +- (void)settingsKeyBackup:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection showKeyBackupRecover:(MXKeyBackupVersion *)keyBackupVersion +{ + [self showKeyBackupRecover:keyBackupVersion]; +} + +- (void)settingsKeyBackup:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection showKeyBackupDeleteConfirm:(MXKeyBackupVersion *)keyBackupVersion +{ + MXWeakify(self); + [currentAlert dismissViewControllerAnimated:NO completion:nil]; + + currentAlert = + [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"settings_key_backup_delete_confirmation_prompt_title", @"Vector", nil) + message:NSLocalizedStringFromTable(@"settings_key_backup_delete_confirmation_prompt_msg", @"Vector", nil) + preferredStyle:UIAlertControllerStyleAlert]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] + style:UIAlertActionStyleCancel + handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + self->currentAlert = nil; + }]]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"settings_key_backup_button_delete", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + self->currentAlert = nil; + + [self->keyBackupSection deleteWithKeyBackupVersion:keyBackupVersion]; + }]]; + + [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCDeleteKeyBackup"]; + [self presentViewController:currentAlert animated:YES completion:nil]; +} + +- (void)settingsKeyBackup:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection showActivityIndicator:(BOOL)show +{ + if (show) + { + [self startActivityIndicator]; + } + else + { + [self stopActivityIndicator]; + } +} + +- (void)settingsKeyBackup:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection showError:(NSError *)error +{ + [[AppDelegate theDelegate] showErrorAsAlert:error]; +} + +#pragma mark - KeyBackupRecoverCoordinatorBridgePresenter + +- (void)showKeyBackupSetupFromSignOutFlow:(BOOL)showFromSignOutFlow +{ + keyBackupSetupCoordinatorBridgePresenter = [[KeyBackupSetupCoordinatorBridgePresenter alloc] initWithSession:self.mainSession]; + + [keyBackupSetupCoordinatorBridgePresenter presentFrom:self + isStartedFromSignOut:showFromSignOutFlow + animated:true]; + + keyBackupSetupCoordinatorBridgePresenter.delegate = self; +} + +- (void)keyBackupSetupCoordinatorBridgePresenterDelegateDidCancel:(KeyBackupSetupCoordinatorBridgePresenter *)bridgePresenter { + [keyBackupSetupCoordinatorBridgePresenter dismissWithAnimated:true]; + keyBackupSetupCoordinatorBridgePresenter = nil; +} + +- (void)keyBackupSetupCoordinatorBridgePresenterDelegateDidSetupRecoveryKey:(KeyBackupSetupCoordinatorBridgePresenter *)bridgePresenter { + [keyBackupSetupCoordinatorBridgePresenter dismissWithAnimated:true]; + keyBackupSetupCoordinatorBridgePresenter = nil; + + [keyBackupSection reload]; +} + +#pragma mark - KeyBackupRecoverCoordinatorBridgePresenter + +- (void)showKeyBackupRecover:(MXKeyBackupVersion*)keyBackupVersion +{ + keyBackupRecoverCoordinatorBridgePresenter = [[KeyBackupRecoverCoordinatorBridgePresenter alloc] initWithSession:self.mainSession keyBackupVersion:keyBackupVersion]; + + [keyBackupRecoverCoordinatorBridgePresenter presentFrom:self animated:true]; + keyBackupRecoverCoordinatorBridgePresenter.delegate = self; +} + +- (void)keyBackupRecoverCoordinatorBridgePresenterDidCancel:(KeyBackupRecoverCoordinatorBridgePresenter *)bridgePresenter { + [keyBackupRecoverCoordinatorBridgePresenter dismissWithAnimated:true]; + keyBackupRecoverCoordinatorBridgePresenter = nil; +} + +- (void)keyBackupRecoverCoordinatorBridgePresenterDidRecover:(KeyBackupRecoverCoordinatorBridgePresenter *)bridgePresenter { + [keyBackupRecoverCoordinatorBridgePresenter dismissWithAnimated:true]; + keyBackupRecoverCoordinatorBridgePresenter = nil; +} + +#pragma mark - SignOutAlertPresenterDelegate + +- (void)signOutAlertPresenterDidTapBackupAction:(SignOutAlertPresenter * _Nonnull)presenter +{ + [self showKeyBackupSetupFromSignOutFlow:YES]; +} + +- (void)signOutAlertPresenterDidTapSignOutAction:(SignOutAlertPresenter * _Nonnull)presenter +{ + // Prevent user to perform user interaction in settings when sign out + // TODO: Prevent user interaction in all application (navigation controller and split view controller included) + self.view.userInteractionEnabled = NO; + self.signOutButton.enabled = NO; + + [self startActivityIndicator]; + + MXWeakify(self); + + [[AppDelegate theDelegate] logoutWithConfirmation:NO completion:^(BOOL isLoggedOut) { + MXStrongifyAndReturnIfNil(self); + + [self stopActivityIndicator]; + + self.view.userInteractionEnabled = YES; + self.signOutButton.enabled = YES; + }]; +} + +#pragma mark - SingleImagePickerPresenterDelegate + +- (void)singleImagePickerPresenterDidCancel:(SingleImagePickerPresenter *)presenter +{ + [presenter dismissWithAnimated:YES completion:nil]; + self.imagePickerPresenter = nil; +} + +- (void)singleImagePickerPresenter:(SingleImagePickerPresenter *)presenter didSelectImageData:(NSData *)imageData withUTI:(MXKUTI *)uti +{ + [presenter dismissWithAnimated:YES completion:nil]; + self.imagePickerPresenter = nil; + + newAvatarImage = [UIImage imageWithData:imageData]; + + [self.tableView reloadData]; +} + + +#pragma mark - Identity Server updates + +- (void)registerAccountDataDidChangeIdentityServerNotification +{ + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAccountDataDidChangeIdentityServerNotification:) name:kMXSessionAccountDataDidChangeIdentityServerNotification object:nil]; +} + +- (void)handleAccountDataDidChangeIdentityServerNotification:(NSNotification*)notification +{ + [self refreshSettings]; +} + +#pragma mark - SettingsDiscoveryTableViewSectionDelegate + +- (void)settingsDiscoveryTableViewSectionDidUpdate:(SettingsDiscoveryTableViewSection *)settingsDiscoveryTableViewSection +{ + [self.tableView reloadData]; +} + +- (MXKTableViewCell *)settingsDiscoveryTableViewSection:(SettingsDiscoveryTableViewSection *)settingsDiscoveryTableViewSection tableViewCellClass:(Class)tableViewCellClass forRow:(NSInteger)forRow +{ + MXKTableViewCell *tableViewCell; + + if ([tableViewCellClass isEqual:[MXKTableViewCell class]]) + { + tableViewCell = [self getDefaultTableViewCell:self.tableView]; + } + else if ([tableViewCellClass isEqual:[MXKTableViewCellWithTextView class]]) + { + tableViewCell = [self textViewCellForTableView:self.tableView atIndexPath:[NSIndexPath indexPathForRow:forRow inSection:SETTINGS_SECTION_DISCOVERY_INDEX]]; + } + else if ([tableViewCellClass isEqual:[MXKTableViewCellWithButton class]]) + { + MXKTableViewCellWithButton *cell = [self.tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; + + if (!cell) + { + cell = [[MXKTableViewCellWithButton alloc] init]; + } + else + { + // Fix https://github.com/vector-im/riot-ios/issues/1354 + cell.mxkButton.titleLabel.text = nil; + } + + cell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; + [cell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; + + tableViewCell = cell; + } + else if ([tableViewCellClass isEqual:[MXKTableViewCellWithLabelAndSwitch class]]) + { + tableViewCell = [self getLabelAndSwitchCell:self.tableView forIndexPath:[NSIndexPath indexPathForRow:forRow inSection:SETTINGS_SECTION_DISCOVERY_INDEX]]; + } + + return tableViewCell; +} + +#pragma mark - SettingsDiscoveryViewModelCoordinatorDelegate + +- (void)settingsDiscoveryViewModel:(SettingsDiscoveryViewModel *)viewModel didSelectThreePidWith:(NSString *)medium and:(NSString *)address +{ + SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter *discoveryThreePidDetailsPresenter = [[SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter alloc] initWithSession:self.mainSession medium:medium adress:address]; + + MXWeakify(self); + + [discoveryThreePidDetailsPresenter pushFrom:self.navigationController animated:YES popCompletion:^{ + MXStrongifyAndReturnIfNil(self); + + self.discoveryThreePidDetailsPresenter = nil; + }]; + + self.discoveryThreePidDetailsPresenter = discoveryThreePidDetailsPresenter; +} + +- (void)settingsDiscoveryViewModelDidTapUserSettingsLink:(SettingsDiscoveryViewModel *)viewModel +{ + NSIndexPath *discoveryIndexPath = [NSIndexPath indexPathForRow:userSettingsNewEmailIndex inSection:SETTINGS_SECTION_USER_SETTINGS_INDEX]; + [self.tableView scrollToRowAtIndexPath:discoveryIndexPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; +} + + +#pragma mark - Identity Server + +- (void)showIdentityServerSettingsScreen +{ + identityServerSettingsCoordinatorBridgePresenter = [[SettingsIdentityServerCoordinatorBridgePresenter alloc] initWithSession:self.mainSession]; + + [identityServerSettingsCoordinatorBridgePresenter pushFrom:self.navigationController animated:YES popCompletion:nil]; + identityServerSettingsCoordinatorBridgePresenter.delegate = self; +} + +#pragma mark - SettingsIdentityServerCoordinatorBridgePresenterDelegate + +- (void)settingsIdentityServerCoordinatorBridgePresenterDelegateDidComplete:(SettingsIdentityServerCoordinatorBridgePresenter *)coordinatorBridgePresenter +{ + identityServerSettingsCoordinatorBridgePresenter = nil; + [self refreshSettings]; +} + +@end diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index ca9430d3bf..0bbd570caf 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -32,6 +32,7 @@ #import "CountryPickerViewController.h" #import "LanguagePickerViewController.h" #import "DeactivateAccountViewController.h" +#import "SecurityViewController.h" #import "NBPhoneNumberUtil.h" #import "RageShakeManager.h" @@ -51,6 +52,7 @@ { SETTINGS_SECTION_SIGN_OUT_INDEX = 0, SETTINGS_SECTION_USER_SETTINGS_INDEX, + SETTINGS_SECTION_SECURITY_INDEX, SETTINGS_SECTION_NOTIFICATIONS_SETTINGS_INDEX, SETTINGS_SECTION_CALLS_INDEX, SETTINGS_SECTION_DISCOVERY_INDEX, @@ -152,6 +154,12 @@ DEVICES_DESCRIPTION_INDEX = 0 }; +enum +{ + SECURITY_BUTTON_INDEX = 0, + SECURITY_COUNT +}; + #define SECTION_TITLE_PADDING_WHEN_HIDDEN 0.01f typedef void (^blockSettingsViewController_onReadyToDestroy)(void); @@ -1483,6 +1491,11 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger { count = 1; } + else if (section == SETTINGS_SECTION_SECURITY_INDEX) + { + count = SECURITY_COUNT; + } + return count; } @@ -2555,6 +2568,17 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N { cell = [keyBackupSection cellForRowAtRow:row]; } + else if (section == SETTINGS_SECTION_SECURITY_INDEX) + { + switch (row) + { + case SECURITY_BUTTON_INDEX: + cell = [self getDefaultTableViewCell:tableView]; + cell.textLabel.text = NSLocalizedStringFromTable(@"Security", @"Vector", nil); + cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + break; + } + } else if (section == SETTINGS_SECTION_DEACTIVATE_ACCOUNT_INDEX) { MXKTableViewCellWithButton *deactivateAccountBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; @@ -2675,11 +2699,15 @@ - (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSectio return NSLocalizedStringFromTable(@"settings_key_backup", @"Vector", nil); } } + else if (section == SETTINGS_SECTION_SECURITY_INDEX) + { + return NSLocalizedStringFromTable(@"SECURITY", @"Vector", nil); + } else if (section == SETTINGS_SECTION_DEACTIVATE_ACCOUNT_INDEX) { return NSLocalizedStringFromTable(@"settings_deactivate_my_account", @"Vector", nil); } - + return nil; } @@ -3021,6 +3049,19 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath [self pushViewController:countryPicker]; } } + else if (section == SETTINGS_SECTION_SECURITY_INDEX) + { + switch (row) + { + case SECURITY_BUTTON_INDEX: + { + SecurityViewController *securityViewController = [SecurityViewController instantiateWithMatrixSession:self.mainSession]; + + [self pushViewController:securityViewController]; + break; + } + } + } [tableView deselectRowAtIndexPath:indexPath animated:YES]; } diff --git a/Tools/SwiftGen/swiftgen-config.yml b/Tools/SwiftGen/swiftgen-config.yml index bb711f244e..0c4d88b3d1 100755 --- a/Tools/SwiftGen/swiftgen-config.yml +++ b/Tools/SwiftGen/swiftgen-config.yml @@ -4,7 +4,7 @@ ib: - inputs: - ../Tools/Templates/buildable/ - Modules/ - filter: ^((?!DeactivateAccountViewController).)*\.(storyboard) + filter: ^((?!(DeactivateAccountViewController|Security)).)*\.(storyboard) outputs: - templateName: scenes-swift4 output: Storyboards.swift @@ -28,4 +28,4 @@ plist: templateName: runtime-swift4 output: RiotDefaults.swift params: - enumName: RiotDefaults \ No newline at end of file + enumName: RiotDefaults From 133e26af36041dd893cebb8001f464bf5f4f439d Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 28 Jan 2020 22:05:11 +0100 Subject: [PATCH 111/282] Settings > Security: Remove useless imported code --- .../Settings/Security/Security.storyboard | 6 +- .../Security/SecurityViewController.h | 2 +- .../Security/SecurtiyViewController.m | 3891 +---------------- 3 files changed, 211 insertions(+), 3688 deletions(-) diff --git a/Riot/Modules/Settings/Security/Security.storyboard b/Riot/Modules/Settings/Security/Security.storyboard index aed7941023..b3db6d0f21 100644 --- a/Riot/Modules/Settings/Security/Security.storyboard +++ b/Riot/Modules/Settings/Security/Security.storyboard @@ -13,13 +13,13 @@ - + - + - + diff --git a/Riot/Modules/Settings/Security/SecurityViewController.h b/Riot/Modules/Settings/Security/SecurityViewController.h index 673f015432..de3aefaa27 100644 --- a/Riot/Modules/Settings/Security/SecurityViewController.h +++ b/Riot/Modules/Settings/Security/SecurityViewController.h @@ -1,5 +1,5 @@ /* - Copyright 2019 New Vector Ltd + Copyright 2020 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. diff --git a/Riot/Modules/Settings/Security/SecurtiyViewController.m b/Riot/Modules/Settings/Security/SecurtiyViewController.m index 5844d146f9..2eb9a26783 100644 --- a/Riot/Modules/Settings/Security/SecurtiyViewController.m +++ b/Riot/Modules/Settings/Security/SecurtiyViewController.m @@ -1,5 +1,5 @@ /* - Copyright 2019 New Vector Ltd + Copyright 2020 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. @@ -47,97 +47,12 @@ enum { - SETTINGS_SECTION_SIGN_OUT_INDEX = 0, - SETTINGS_SECTION_USER_SETTINGS_INDEX, - SETTINGS_SECTION_NOTIFICATIONS_SETTINGS_INDEX, - SETTINGS_SECTION_CALLS_INDEX, - SETTINGS_SECTION_DISCOVERY_INDEX, - SETTINGS_SECTION_IDENTITY_SERVER_INDEX, - SETTINGS_SECTION_CONTACTS_INDEX, - SETTINGS_SECTION_IGNORED_USERS_INDEX, - SETTINGS_SECTION_INTEGRATIONS_INDEX, - SETTINGS_SECTION_USER_INTERFACE_INDEX, - SETTINGS_SECTION_ADVANCED_INDEX, - SETTINGS_SECTION_OTHER_INDEX, - SETTINGS_SECTION_LABS_INDEX, + SETTINGS_SECTION_DEVICES_INDEX, SETTINGS_SECTION_CRYPTOGRAPHY_INDEX, SETTINGS_SECTION_KEYBACKUP_INDEX, - SETTINGS_SECTION_DEVICES_INDEX, - SETTINGS_SECTION_FLAIR_INDEX, - SETTINGS_SECTION_DEACTIVATE_ACCOUNT_INDEX, SETTINGS_SECTION_COUNT }; -enum -{ - NOTIFICATION_SETTINGS_ENABLE_PUSH_INDEX = 0, - NOTIFICATION_SETTINGS_SHOW_DECODED_CONTENT, - NOTIFICATION_SETTINGS_GLOBAL_SETTINGS_INDEX, - NOTIFICATION_SETTINGS_PIN_MISSED_NOTIFICATIONS_INDEX, - NOTIFICATION_SETTINGS_PIN_UNREAD_INDEX, - //NOTIFICATION_SETTINGS_CONTAINING_MY_USER_NAME_INDEX, - //NOTIFICATION_SETTINGS_CONTAINING_MY_DISPLAY_NAME_INDEX, - //NOTIFICATION_SETTINGS_SENT_TO_ME_INDEX, - //NOTIFICATION_SETTINGS_INVITED_TO_ROOM_INDEX, - //NOTIFICATION_SETTINGS_PEOPLE_LEAVE_JOIN_INDEX, - //NOTIFICATION_SETTINGS_CALL_INVITATION_INDEX, - NOTIFICATION_SETTINGS_COUNT -}; - -enum -{ - CALLS_ENABLE_CALLKIT_INDEX = 0, - CALLS_CALLKIT_DESCRIPTION_INDEX, - CALLS_ENABLE_STUN_SERVER_FALLBACK_INDEX, - CALLS_STUN_SERVER_FALLBACK_DESCRIPTION_INDEX, - CALLS_COUNT -}; - -enum -{ - INTEGRATIONS_INDEX, - INTEGRATIONS_DESCRIPTION_INDEX, - INTEGRATIONS_COUNT -}; - -enum -{ - USER_INTERFACE_LANGUAGE_INDEX = 0, - USER_INTERFACE_THEME_INDEX, - USER_INTERFACE_COUNT -}; - -enum -{ - IDENTITY_SERVER_INDEX, - IDENTITY_SERVER_DESCRIPTION_INDEX, - IDENTITY_SERVER_COUNT -}; - -enum -{ - OTHER_VERSION_INDEX = 0, - OTHER_OLM_VERSION_INDEX, - OTHER_COPYRIGHT_INDEX, - OTHER_TERM_CONDITIONS_INDEX, - OTHER_PRIVACY_INDEX, - OTHER_THIRD_PARTY_INDEX, - OTHER_CRASH_REPORT_INDEX, - OTHER_ENABLE_RAGESHAKE_INDEX, - OTHER_MARK_ALL_AS_READ_INDEX, - OTHER_CLEAR_CACHE_INDEX, - OTHER_REPORT_BUG_INDEX, - OTHER_COUNT -}; - -enum -{ - LABS_USE_ROOM_MEMBERS_LAZY_LOADING_INDEX = 0, - LABS_USE_JITSI_WIDGET_INDEX, - LABS_CROSS_SIGNING_INDEX, - LABS_COUNT -}; - enum { CRYPTOGRAPHY_INFO_INDEX = 0, CRYPTOGRAPHY_BLACKLIST_UNVERIFIED_DEVICES_INDEX, @@ -326,7 +241,7 @@ - (void)viewDidLoad [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. - self.navigationItem.title = NSLocalizedStringFromTable(@"settings_title", @"Vector", nil); + self.navigationItem.title = NSLocalizedStringFromTable(@"security_title", @"Vector", nil); // Remove back bar button title when pushing a view controller self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; @@ -341,44 +256,6 @@ - (void)viewDidLoad // Enable self sizing cells self.tableView.rowHeight = UITableViewAutomaticDimension; self.tableView.estimatedRowHeight = 50; - - // Add observer to handle removed accounts - removedAccountObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKAccountManagerDidRemoveAccountNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - - if ([MXKAccountManager sharedManager].accounts.count) - { - // Refresh table to remove this account - [self refreshSettings]; - } - - }]; - - // Add observer to handle accounts update - accountUserInfoObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKAccountUserInfoDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - - [self stopActivityIndicator]; - - [self refreshSettings]; - - }]; - - // Add observer to push settings - pushInfoUpdateObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKAccountPushKitActivityDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - - [self stopActivityIndicator]; - - [self refreshSettings]; - - }]; - - [self registerAccountDataDidChangeIdentityServerNotification]; - - // Add each matrix session, to update the view controller appearance according to mx sessions state - NSArray *sessions = [AppDelegate theDelegate].mxSessions; - for (MXSession *mxSession in sessions) - { - [self addMatrixSession:mxSession]; - } if (self.mainSession.crypto.backup) { @@ -392,16 +269,6 @@ - (void)viewDidLoad } } - [self setupDiscoverySection]; - - groupsDataSource = [[GroupsDataSource alloc] initWithMatrixSession:self.mainSession]; - [groupsDataSource finalizeInitialization]; - groupsDataSource.delegate = self; - - self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:@selector(onSave:)]; - self.navigationItem.rightBarButtonItem.accessibilityIdentifier=@"SettingsVCNavBarSaveButton"; - - // Observe user interface theme change. kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { @@ -409,9 +276,6 @@ - (void)viewDidLoad }]; [self userInterfaceThemeDidChange]; - - self.signOutAlertPresenter = [SignOutAlertPresenter new]; - self.signOutAlertPresenter.delegate = self; } - (void)userInterfaceThemeDidChange @@ -522,9 +386,6 @@ - (void)viewWillAppear:(BOOL)animated // Refresh display [self refreshSettings]; - // Refresh linked emails and phone numbers in parallel - [self loadAccount3PIDs]; - // Refresh the current device information in parallel [self loadCurrentDeviceInformation]; @@ -667,352 +528,6 @@ - (void)reset } } --(void)setNewEmailEditingEnabled:(BOOL)newEmailEditingEnabled -{ - if (newEmailEditingEnabled != _newEmailEditingEnabled) - { - // Update the flag - _newEmailEditingEnabled = newEmailEditingEnabled; - - if (!newEmailEditingEnabled) - { - // Dismiss the keyboard - [newEmailTextField resignFirstResponder]; - newEmailTextField = nil; - } - - dispatch_async(dispatch_get_main_queue(), ^{ - - [self.tableView beginUpdates]; - - // Refresh the corresponding table view cell with animation - [self.tableView reloadRowsAtIndexPaths:@[ - [NSIndexPath indexPathForRow:userSettingsNewEmailIndex inSection:SETTINGS_SECTION_USER_SETTINGS_INDEX] - ] - withRowAnimation:UITableViewRowAnimationFade]; - - [self.tableView endUpdates]; - }); - } -} - --(void)setNewPhoneEditingEnabled:(BOOL)newPhoneEditingEnabled -{ - if (newPhoneEditingEnabled != _newPhoneEditingEnabled) - { - // Update the flag - _newPhoneEditingEnabled = newPhoneEditingEnabled; - - if (!newPhoneEditingEnabled) - { - // Dismiss the keyboard - [newPhoneNumberCell.mxkTextField resignFirstResponder]; - newPhoneNumberCell = nil; - } - - dispatch_async(dispatch_get_main_queue(), ^{ - - [self.tableView beginUpdates]; - - // Refresh the corresponding table view cell with animation - [self.tableView reloadRowsAtIndexPaths:@[ - [NSIndexPath indexPathForRow:userSettingsNewPhoneIndex inSection:SETTINGS_SECTION_USER_SETTINGS_INDEX] - ] - withRowAnimation:UITableViewRowAnimationFade]; - - [self.tableView endUpdates]; - }); - } -} - -- (void)showValidationEmailDialogWithMessage:(NSString*)message for3PidAddSession:(MX3PidAddSession*)threePidAddSession threePidAddManager:(MX3PidAddManager*)threePidAddManager password:(NSString*)password -{ - MXWeakify(self); - [currentAlert dismissViewControllerAnimated:NO completion:nil]; - currentAlert = [UIAlertController alertControllerWithTitle:[NSBundle mxk_localizedStringForKey:@"account_email_validation_title"] message:message preferredStyle:UIAlertControllerStyleAlert]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - MXStrongifyAndReturnIfNil(self); - self->currentAlert = nil; - [self stopActivityIndicator]; - - // Reset new email adding - self.newEmailEditingEnabled = NO; - }]]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"continue"] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - MXStrongifyAndReturnIfNil(self); - [self tryFinaliseAddEmailSession:threePidAddSession withPassword:password threePidAddManager:threePidAddManager]; - }]]; - - [currentAlert mxk_setAccessibilityIdentifier:@"SettingsVCEmailValidationAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; -} - -- (void)tryFinaliseAddEmailSession:(MX3PidAddSession*)threePidAddSession withPassword:(NSString*)password threePidAddManager:(MX3PidAddManager*)threePidAddManager -{ - self->is3PIDBindingInProgress = YES; - - [threePidAddManager tryFinaliseAddEmailSession:threePidAddSession withPassword:password success:^{ - - self->is3PIDBindingInProgress = NO; - - // Check whether destroy has been called during email binding - if (self->onReadyToDestroyHandler) - { - // Ready to destroy - self->onReadyToDestroyHandler(); - self->onReadyToDestroyHandler = nil; - } - else - { - self->currentAlert = nil; - - [self stopActivityIndicator]; - - // Reset new email adding - self.newEmailEditingEnabled = NO; - - // Update linked emails - [self loadAccount3PIDs]; - } - - } failure:^(NSError * _Nonnull error) { - NSLog(@"[SettingsViewController] tryFinaliseAddEmailSession: Failed to bind email"); - - MXError *mxError = [[MXError alloc] initWithNSError:error]; - if (mxError && [mxError.errcode isEqualToString:kMXErrCodeStringForbidden]) - { - NSLog(@"[SettingsViewController] tryFinaliseAddEmailSession: Wrong password"); - - // Ask password again - self->currentAlert = [UIAlertController alertControllerWithTitle:nil - message:NSLocalizedStringFromTable(@"settings_add_3pid_invalid_password_message", @"Vector", nil) - preferredStyle:UIAlertControllerStyleAlert]; - - [self->currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"retry", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - self->currentAlert = nil; - - [self requestAccountPasswordWithTitle:NSLocalizedStringFromTable(@"settings_add_3pid_password_title_email", @"Vector", nil) - message:NSLocalizedStringFromTable(@"settings_add_3pid_password_message", @"Vector", nil) - onComplete:^(NSString *password) { - [self tryFinaliseAddEmailSession:threePidAddSession withPassword:password threePidAddManager:threePidAddManager]; - }]; - }]]; - - [self presentViewController:self->currentAlert animated:YES completion:nil]; - - return; - } - - self->is3PIDBindingInProgress = NO; - - // Check whether destroy has been called during email binding - if (self->onReadyToDestroyHandler) - { - // Ready to destroy - self->onReadyToDestroyHandler(); - self->onReadyToDestroyHandler = nil; - } - else - { - self->currentAlert = nil; - - // Display the same popup again if the error is M_THREEPID_AUTH_FAILED - MXError *mxError = [[MXError alloc] initWithNSError:error]; - if (mxError && [mxError.errcode isEqualToString:kMXErrCodeStringThreePIDAuthFailed]) - { - [self showValidationEmailDialogWithMessage:[NSBundle mxk_localizedStringForKey:@"account_email_validation_error"] - for3PidAddSession:threePidAddSession - threePidAddManager:threePidAddManager - password:password]; - } - else - { - [self stopActivityIndicator]; - - // Notify user - NSString *myUserId = self.mainSession.myUser.userId; // TODO: Hanlde multi-account - [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; - } - } - }]; -} - -- (void)showValidationMsisdnDialogWithMessage:(NSString*)message for3PidAddSession:(MX3PidAddSession*)threePidAddSession threePidAddManager:(MX3PidAddManager*)threePidAddManager password:(NSString*)password -{ - MXWeakify(self); - - [currentAlert dismissViewControllerAnimated:NO completion:nil]; - currentAlert = [UIAlertController alertControllerWithTitle:[NSBundle mxk_localizedStringForKey:@"account_msisdn_validation_title"] message:message preferredStyle:UIAlertControllerStyleAlert]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - MXStrongifyAndReturnIfNil(self); - - self->currentAlert = nil; - - [self stopActivityIndicator]; - - // Reset new phone adding - self.newPhoneEditingEnabled = NO; - }]]; - - [currentAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) { - textField.secureTextEntry = NO; - textField.placeholder = nil; - textField.keyboardType = UIKeyboardTypeDecimalPad; - }]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"submit"] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - - MXStrongifyAndReturnIfNil(self); - - NSString *smsCode = [self->currentAlert textFields].firstObject.text; - - self->currentAlert = nil; - - if (smsCode.length) - { - [self finaliseAddPhoneNumberSession:threePidAddSession withToken:smsCode andPassword:password message:message threePidAddManager:threePidAddManager]; - } - else - { - // Ask again the sms token - [self showValidationMsisdnDialogWithMessage:message for3PidAddSession:threePidAddSession threePidAddManager:threePidAddManager password:password]; - } - }]]; - - [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCMsisdnValidationAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; -} - -- (void)finaliseAddPhoneNumberSession:(MX3PidAddSession*)threePidAddSession withToken:(NSString*)token andPassword:(NSString*)password message:(NSString*)message threePidAddManager:(MX3PidAddManager*)threePidAddManager -{ - self->is3PIDBindingInProgress = YES; - - [threePidAddManager finaliseAddPhoneNumberSession:threePidAddSession withToken:token password:password success:^{ - - self->is3PIDBindingInProgress = NO; - - // Check whether destroy has been called during the binding - if (self->onReadyToDestroyHandler) - { - // Ready to destroy - self->onReadyToDestroyHandler(); - self->onReadyToDestroyHandler = nil; - } - else - { - [self stopActivityIndicator]; - - // Reset new phone adding - self.newPhoneEditingEnabled = NO; - - // Update linked 3pids - [self loadAccount3PIDs]; - } - - } failure:^(NSError * _Nonnull error) { - - NSLog(@"[SettingsViewController] finaliseAddPhoneNumberSession: Failed to submit the sms token"); - - MXError *mxError = [[MXError alloc] initWithNSError:error]; - if (mxError && [mxError.errcode isEqualToString:kMXErrCodeStringForbidden]) - { - NSLog(@"[SettingsViewController] finaliseAddPhoneNumberSession: Wrong password"); - - // Ask password again - self->currentAlert = [UIAlertController alertControllerWithTitle:nil - message:NSLocalizedStringFromTable(@"settings_add_3pid_invalid_password_message", @"Vector", nil) - preferredStyle:UIAlertControllerStyleAlert]; - - [self->currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"retry", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - self->currentAlert = nil; - - [self requestAccountPasswordWithTitle:NSLocalizedStringFromTable(@"settings_add_3pid_password_title_msidsn", @"Vector", nil) - message:NSLocalizedStringFromTable(@"settings_add_3pid_password_message", @"Vector", nil) - onComplete:^(NSString *password) { - [self finaliseAddPhoneNumberSession:threePidAddSession withToken:token andPassword:password message:message threePidAddManager:threePidAddManager]; - }]; - }]]; - - [self presentViewController:self->currentAlert animated:YES completion:nil]; - - return; - } - - self->is3PIDBindingInProgress = NO; - - // Check whether destroy has been called during phone binding - if (self->onReadyToDestroyHandler) - { - // Ready to destroy - self->onReadyToDestroyHandler(); - self->onReadyToDestroyHandler = nil; - } - else - { - // Ignore connection cancellation error - if (([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled)) - { - [self stopActivityIndicator]; - return; - } - - // Alert user - NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey]; - NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey]; - if (!title) - { - if (msg) - { - title = msg; - msg = nil; - } - else - { - title = [NSBundle mxk_localizedStringForKey:@"error"]; - } - } - - - self->currentAlert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert]; - - [self->currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - self->currentAlert = nil; - - // Ask again the sms token - [self showValidationMsisdnDialogWithMessage:message for3PidAddSession:threePidAddSession threePidAddManager:threePidAddManager password:password]; - }]]; - - [self->currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCErrorAlert"]; - [self presentViewController:self->currentAlert animated:YES completion:nil]; - } - }]; -} - -- (void)loadAccount3PIDs -{ - // Refresh the account 3PIDs list - MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; - [account load3PIDs:^{ - - NSArray *thirdPartyIdentifiers = account.threePIDs ?: @[]; - [self.settingsDiscoveryViewModel updateWithThirdPartyIdentifiers:thirdPartyIdentifiers]; - - // Refresh all the table (A slide down animation is observed when we limit the refresh to the concerned section). - // Note: The use of 'reloadData' handles the case where the account has been logged out. - [self refreshSettings]; - - } failure:^(NSError *error) { - - // Display the data that has been loaded last time - // Note: The use of 'reloadData' handles the case where the account has been logged out. - [self refreshSettings]; - - }]; -} - - (void)loadCurrentDeviceInformation { // Refresh the current device information @@ -1226,105 +741,20 @@ - (void)refreshSettings } } -- (void)formatNewPhoneNumber -{ - if (newPhoneNumber) - { - NSString *formattedNumber = [[NBPhoneNumberUtil sharedInstance] format:newPhoneNumber numberFormat:NBEPhoneNumberFormatINTERNATIONAL error:nil]; - NSString *prefix = newPhoneNumberCell.mxkLabel.text; - if ([formattedNumber hasPrefix:prefix]) - { - // Format the display phone number - newPhoneNumberCell.mxkTextField.text = [formattedNumber substringFromIndex:prefix.length]; - } - } -} - -- (void)setupDiscoverySection +- (void)requestAccountPasswordWithTitle:(NSString*)title message:(NSString*)message onComplete:(void (^)(NSString *password))onComplete { - MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; - - NSArray *thirdPartyIdentifiers = account.threePIDs ?: @[]; - - SettingsDiscoveryViewModel *viewModel = [[SettingsDiscoveryViewModel alloc] initWithSession:self.mainSession thirdPartyIdentifiers:thirdPartyIdentifiers]; - viewModel.coordinatorDelegate = self; - - SettingsDiscoveryTableViewSection *discoverySection = [[SettingsDiscoveryTableViewSection alloc] initWithViewModel:viewModel]; - discoverySection.delegate = self; - - self.settingsDiscoveryViewModel = viewModel; - self.settingsDiscoveryTableViewSection = discoverySection; -} - -#pragma mark - 3Pid Add + [currentAlert dismissViewControllerAnimated:NO completion:nil]; --(void)checkAuthenticationFlowForAdding:(MX3PIDMedium)medium withSession:(MXSession*)session onComplete:(void (^)(NSString *password))onComplete -{ - [self startActivityIndicator]; + // Prompt the user before deleting the device. + currentAlert = [UIAlertController alertControllerWithTitle:title + message:message + preferredStyle:UIAlertControllerStyleAlert]; - [session.threePidAddManager authenticationFlowForAdd3PidWithSuccess:^(NSArray * _Nullable flows) { - [self stopActivityIndicator]; - - if (flows) - { - // We support only "m.login.password" - BOOL hasPasswordFlow = NO; - for (MXLoginFlow *flow in flows) - { - if ([flow.stages containsObject:kMXLoginFlowTypePassword]) - { - hasPasswordFlow = YES; - break; - } - } - - if (hasPasswordFlow) - { - // Ask password to the user while we are here - NSString *title = NSLocalizedStringFromTable(@"settings_add_3pid_password_title_email", @"Vector", nil); - if ([medium isEqualToString:kMX3PIDMediumMSISDN]) - { - title = NSLocalizedStringFromTable(@"settings_add_3pid_password_title_msidsn", @"Vector", nil); - } - - [self requestAccountPasswordWithTitle:title - message:NSLocalizedStringFromTable(@"settings_add_3pid_password_message", @"Vector", nil) - onComplete:onComplete]; - } - else - { - // The user needs to use Riot-web - NSString *appName = [[NSBundle mainBundle] infoDictionary][@"CFBundleDisplayName"]; - NSString *message = [NSString stringWithFormat:NSLocalizedStringFromTable(@"error_not_supported_on_mobile", @"Vector", nil), appName]; - [[AppDelegate theDelegate] showAlertWithTitle:nil message:message]; - } - } - else - { - // No auth - onComplete(nil); - } - - } failure:^(NSError * _Nonnull error) { - [self stopActivityIndicator]; - [[AppDelegate theDelegate] showErrorAsAlert:error]; - }]; -} - -- (void)requestAccountPasswordWithTitle:(NSString*)title message:(NSString*)message onComplete:(void (^)(NSString *password))onComplete -{ - [currentAlert dismissViewControllerAnimated:NO completion:nil]; - - // Prompt the user before deleting the device. - currentAlert = [UIAlertController alertControllerWithTitle:title - message:message - preferredStyle:UIAlertControllerStyleAlert]; - - [currentAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) { - textField.secureTextEntry = YES; - textField.placeholder = nil; - textField.keyboardType = UIKeyboardTypeDefault; - }]; + [currentAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.secureTextEntry = YES; + textField.placeholder = nil; + textField.keyboardType = UIKeyboardTypeDefault; + }]; MXWeakify(self); [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] @@ -1363,9 +793,6 @@ - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - // update the save button if there is an update - [self updateSaveButtonStatus]; - return SETTINGS_SECTION_COUNT; } @@ -1373,110 +800,7 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger { NSInteger count = 0; - if (section == SETTINGS_SECTION_SIGN_OUT_INDEX) - { - count = 1; - } - else if (section == SETTINGS_SECTION_USER_SETTINGS_INDEX) - { - MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; - - userSettingsProfilePictureIndex = 0; - userSettingsDisplayNameIndex = 1; - userSettingsChangePasswordIndex = 2; - - // Hide some unsupported account settings - userSettingsFirstNameIndex = -1; - userSettingsSurnameIndex = -1; - userSettingsNightModeSepIndex = -1; - userSettingsNightModeIndex = -1; - - userSettingsEmailStartIndex = 3; - userSettingsNewEmailIndex = userSettingsEmailStartIndex + account.linkedEmails.count; - userSettingsPhoneStartIndex = userSettingsNewEmailIndex + 1; - userSettingsNewPhoneIndex = userSettingsPhoneStartIndex + account.linkedPhoneNumbers.count; - userSettingsThreePidsInformation = userSettingsNewPhoneIndex + 1; - - count = userSettingsThreePidsInformation + 1; - } - else if (section == SETTINGS_SECTION_NOTIFICATIONS_SETTINGS_INDEX) - { - count = NOTIFICATION_SETTINGS_COUNT; - } - else if (section == SETTINGS_SECTION_CALLS_INDEX) - { - count = CALLS_COUNT; - - if (!RiotSettings.shared.stunServerFallback) - { - count -= 2; - } - } - else if (section == SETTINGS_SECTION_DISCOVERY_INDEX) - { - count = self.settingsDiscoveryTableViewSection.numberOfRows; - } - else if (section == SETTINGS_SECTION_IDENTITY_SERVER_INDEX) - { - count = IDENTITY_SERVER_COUNT; - } - else if (section == SETTINGS_SECTION_INTEGRATIONS_INDEX) - { - count = INTEGRATIONS_COUNT; - } - else if (section == SETTINGS_SECTION_USER_INTERFACE_INDEX) - { - count = USER_INTERFACE_COUNT; - } - else if (section == SETTINGS_SECTION_IGNORED_USERS_INDEX) - { - if ([AppDelegate theDelegate].mxSessions.count > 0) - { - MXSession* session = [AppDelegate theDelegate].mxSessions[0]; - count = session.ignoredUsers.count; - } - else - { - count = 0; - } - } - else if (section == SETTINGS_SECTION_CONTACTS_INDEX) - { - localContactsSyncIndex = count++; - - if ([MXKAppSettings standardAppSettings].syncLocalContacts) - { - localContactsPhoneBookCountryIndex = count++; - } - else - { - localContactsPhoneBookCountryIndex = -1; - } - } - else if (section == SETTINGS_SECTION_ADVANCED_INDEX) - { - count = 1; - } - else if (section == SETTINGS_SECTION_OTHER_INDEX) - { - count = OTHER_COUNT; - } - else if (section == SETTINGS_SECTION_LABS_INDEX) - { - count = LABS_COUNT; - } - else if (section == SETTINGS_SECTION_FLAIR_INDEX) - { - // Check whether some joined groups are available - if ([groupsDataSource numberOfSectionsInTableView:tableView]) - { - if (groupsDataSource.joinedGroupsSection != -1) - { - count = [groupsDataSource tableView:tableView numberOfRowsInSection:groupsDataSource.joinedGroupsSection]; - } - } - } - else if (section == SETTINGS_SECTION_DEVICES_INDEX) + if (section == SETTINGS_SECTION_DEVICES_INDEX) { count = devicesArray.count; if (count) @@ -1501,10 +825,7 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger count = keyBackupSection.numberOfRows; } } - else if (section == SETTINGS_SECTION_DEACTIVATE_ACCOUNT_INDEX) - { - count = 1; - } + return count; } @@ -1608,2904 +929,268 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N MXSession* session = [AppDelegate theDelegate].mxSessions[0]; MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; - if (section == SETTINGS_SECTION_SIGN_OUT_INDEX) + if (section == SETTINGS_SECTION_DEVICES_INDEX) { - MXKTableViewCellWithButton *signOutCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; - if (!signOutCell) + if (row == DEVICES_DESCRIPTION_INDEX) { - signOutCell = [[MXKTableViewCellWithButton alloc] init]; + MXKTableViewCell *descriptionCell = [self getDefaultTableViewCell:tableView]; + descriptionCell.textLabel.text = NSLocalizedStringFromTable(@"settings_devices_description", @"Vector", nil); + descriptionCell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + descriptionCell.textLabel.font = [UIFont systemFontOfSize:15]; + descriptionCell.textLabel.numberOfLines = 0; + descriptionCell.contentView.backgroundColor = ThemeService.shared.theme.headerBackgroundColor; + descriptionCell.selectionStyle = UITableViewCellSelectionStyleNone; + + cell = descriptionCell; } else { - // Fix https://github.com/vector-im/riot-ios/issues/1354 - // Do not move this line in prepareForReuse because of https://github.com/vector-im/riot-ios/issues/1323 - signOutCell.mxkButton.titleLabel.text = nil; - } - - NSString* title = NSLocalizedStringFromTable(@"settings_sign_out", @"Vector", nil); - - [signOutCell.mxkButton setTitle:title forState:UIControlStateNormal]; - [signOutCell.mxkButton setTitle:title forState:UIControlStateHighlighted]; - [signOutCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; - signOutCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; - - [signOutCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; - [signOutCell.mxkButton addTarget:self action:@selector(onSignout:) forControlEvents:UIControlEventTouchUpInside]; - signOutCell.mxkButton.accessibilityIdentifier=@"SettingsVCSignOutButton"; - - cell = signOutCell; - } - else if (section == SETTINGS_SECTION_USER_SETTINGS_INDEX) - { - MXMyUser* myUser = session.myUser; - - if (row == userSettingsProfilePictureIndex) - { - MXKTableViewCellWithLabelAndMXKImageView *profileCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithLabelAndMXKImageView defaultReuseIdentifier] forIndexPath:indexPath]; - - profileCell.mxkLabelLeadingConstraint.constant = profileCell.separatorInset.left; - profileCell.mxkImageViewTrailingConstraint.constant = 10; - - profileCell.mxkImageViewWidthConstraint.constant = profileCell.mxkImageViewHeightConstraint.constant = 30; - profileCell.mxkImageViewDisplayBoxType = MXKTableViewCellDisplayBoxTypeCircle; - - if (!profileCell.mxkImageView.gestureRecognizers.count) - { - // tap on avatar to update it - UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onProfileAvatarTap:)]; - [profileCell.mxkImageView addGestureRecognizer:tap]; - } - - profileCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_profile_picture", @"Vector", nil); - profileCell.accessibilityIdentifier=@"SettingsVCProfilPictureStaticText"; - profileCell.mxkLabel.textColor = ThemeService.shared.theme.textPrimaryColor; - - // if the user defines a new avatar - if (newAvatarImage) - { - profileCell.mxkImageView.image = newAvatarImage; - } - else + NSUInteger deviceIndex = row - 1; + + MXKTableViewCell *deviceCell = [self getDefaultTableViewCell:tableView]; + if (deviceIndex < devicesArray.count) { - UIImage* avatarImage = [AvatarGenerator generateAvatarForMatrixItem:myUser.userId withDisplayName:myUser.displayname]; - - if (myUser.avatarUrl) - { - profileCell.mxkImageView.enableInMemoryCache = YES; - - [profileCell.mxkImageView setImageURI:myUser.avatarUrl - withType:nil - andImageOrientation:UIImageOrientationUp - toFitViewSize:profileCell.mxkImageView.frame.size - withMethod:MXThumbnailingMethodCrop - previewImage:avatarImage - mediaManager:session.mediaManager]; - } - else + NSString *name = devicesArray[deviceIndex].displayName; + NSString *deviceId = devicesArray[deviceIndex].deviceId; + deviceCell.textLabel.text = (name.length ? [NSString stringWithFormat:@"%@ (%@)", name, deviceId] : [NSString stringWithFormat:@"(%@)", deviceId]); + deviceCell.textLabel.numberOfLines = 0; + + if ([deviceId isEqualToString:self.mainSession.matrixRestClient.credentials.deviceId]) { - profileCell.mxkImageView.image = avatarImage; + deviceCell.textLabel.font = [UIFont boldSystemFontOfSize:17]; } } - - cell = profileCell; - } - else if (row == userSettingsDisplayNameIndex) - { - MXKTableViewCellWithLabelAndTextField *displaynameCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; - - displaynameCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_display_name", @"Vector", nil); - displaynameCell.mxkTextField.text = myUser.displayname; - - displaynameCell.mxkTextField.tag = row; - displaynameCell.mxkTextField.delegate = self; - [displaynameCell.mxkTextField removeTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; - [displaynameCell.mxkTextField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; - displaynameCell.mxkTextField.accessibilityIdentifier=@"SettingsVCDisplayNameTextField"; - - cell = displaynameCell; - } - else if (row == userSettingsFirstNameIndex) - { - MXKTableViewCellWithLabelAndTextField *firstCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; - - firstCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_first_name", @"Vector", nil); - firstCell.mxkTextField.userInteractionEnabled = NO; - - cell = firstCell; - } - else if (row == userSettingsSurnameIndex) - { - MXKTableViewCellWithLabelAndTextField *surnameCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; - - surnameCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_surname", @"Vector", nil); - surnameCell.mxkTextField.userInteractionEnabled = NO; - - cell = surnameCell; + + cell = deviceCell; } - else if (userSettingsEmailStartIndex <= row && row < userSettingsNewEmailIndex) + + } + else if (section == SETTINGS_SECTION_CRYPTOGRAPHY_INDEX) + { + if (row == CRYPTOGRAPHY_INFO_INDEX) { - MXKTableViewCellWithLabelAndTextField *emailCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; - - emailCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_email_address", @"Vector", nil); - emailCell.mxkTextField.text = account.linkedEmails[row - userSettingsEmailStartIndex]; - emailCell.mxkTextField.userInteractionEnabled = NO; + MXKTableViewCellWithTextView *cryptoCell = [self textViewCellForTableView:tableView atIndexPath:indexPath]; - cell = emailCell; + cryptoCell.mxkTextView.attributedText = [self cryptographyInformation]; + + cell = cryptoCell; } - else if (row == userSettingsNewEmailIndex) + else if (row == CRYPTOGRAPHY_BLACKLIST_UNVERIFIED_DEVICES_INDEX) { - MXKTableViewCellWithLabelAndTextField *newEmailCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; + MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - // Render the cell according to the `newEmailEditingEnabled` property - if (!_newEmailEditingEnabled) - { - newEmailCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_add_email_address", @"Vector", nil); - newEmailCell.mxkTextField.text = nil; - newEmailCell.mxkTextField.userInteractionEnabled = NO; - - newEmailCell.accessoryView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"plus_icon"]]; - } - else - { - newEmailCell.mxkLabel.text = nil; - newEmailCell.mxkTextField.placeholder = NSLocalizedStringFromTable(@"settings_email_address_placeholder", @"Vector", nil); - newEmailCell.mxkTextField.attributedPlaceholder = [[NSAttributedString alloc] - initWithString:newEmailCell.mxkTextField.placeholder - attributes:@{NSForegroundColorAttributeName: ThemeService.shared.theme.placeholderTextColor}]; - newEmailCell.mxkTextField.text = newEmailTextField.text; - newEmailCell.mxkTextField.userInteractionEnabled = YES; - newEmailCell.mxkTextField.keyboardType = UIKeyboardTypeEmailAddress; - newEmailCell.mxkTextField.autocorrectionType = UITextAutocorrectionTypeNo; - newEmailCell.mxkTextField.spellCheckingType = UITextSpellCheckingTypeNo; - newEmailCell.mxkTextField.delegate = self; - newEmailCell.mxkTextField.accessibilityIdentifier=@"SettingsVCAddEmailTextField"; - - [newEmailCell.mxkTextField removeTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; - [newEmailCell.mxkTextField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; - - [newEmailCell.mxkTextField removeTarget:self action:@selector(textFieldDidEnd:) forControlEvents:UIControlEventEditingDidEnd]; - [newEmailCell.mxkTextField addTarget:self action:@selector(textFieldDidEnd:) forControlEvents:UIControlEventEditingDidEnd]; - - // When displaying the textfield the 1st time, open the keyboard - if (!newEmailTextField) - { - newEmailTextField = newEmailCell.mxkTextField; - [self editNewEmailTextField]; - } - else - { - // Update the current text field. - newEmailTextField = newEmailCell.mxkTextField; - } - - UIImage *accessoryViewImage = [MXKTools paintImage:[UIImage imageNamed:@"plus_icon"] withColor:ThemeService.shared.theme.tintColor]; - newEmailCell.accessoryView = [[UIImageView alloc] initWithImage:accessoryViewImage]; - } - - newEmailCell.mxkTextField.tag = row; + labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_crypto_blacklist_unverified_devices", @"Vector", nil); + labelAndSwitchCell.mxkSwitch.on = account.mxSession.crypto.globalBlacklistUnverifiedDevices; + labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + labelAndSwitchCell.mxkSwitch.enabled = YES; + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleBlacklistUnverifiedDevices:) forControlEvents:UIControlEventTouchUpInside]; - cell = newEmailCell; - } - else if (userSettingsPhoneStartIndex <= row && row < userSettingsNewPhoneIndex) - { - MXKTableViewCellWithLabelAndTextField *phoneCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; - - phoneCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_phone_number", @"Vector", nil); - - phoneCell.mxkTextField.text = [MXKTools readableMSISDN:account.linkedPhoneNumbers[row - userSettingsPhoneStartIndex]]; - phoneCell.mxkTextField.userInteractionEnabled = NO; - - cell = phoneCell; + cell = labelAndSwitchCell; } - else if (row == userSettingsNewPhoneIndex) + else if (row == CRYPTOGRAPHY_EXPORT_INDEX) { - // Render the cell according to the `newPhoneEditingEnabled` property - if (!_newPhoneEditingEnabled) + MXKTableViewCellWithButton *exportKeysBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; + if (!exportKeysBtnCell) { - MXKTableViewCellWithLabelAndTextField *newPhoneCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; - - newPhoneCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_add_phone_number", @"Vector", nil); - newPhoneCell.mxkTextField.text = nil; - newPhoneCell.mxkTextField.userInteractionEnabled = NO; - newPhoneCell.accessoryView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"plus_icon"]]; - - cell = newPhoneCell; + exportKeysBtnCell = [[MXKTableViewCellWithButton alloc] init]; } else { - TableViewCellWithPhoneNumberTextField * newPhoneCell = [self.tableView dequeueReusableCellWithIdentifier:[TableViewCellWithPhoneNumberTextField defaultReuseIdentifier] forIndexPath:indexPath]; - - [newPhoneCell.countryCodeButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; - [newPhoneCell.countryCodeButton addTarget:self action:@selector(selectPhoneNumberCountry:) forControlEvents:UIControlEventTouchUpInside]; - newPhoneCell.countryCodeButton.accessibilityIdentifier = @"SettingsVCPhoneCountryButton"; - - newPhoneCell.mxkLabel.font = newPhoneCell.mxkTextField.font = [UIFont systemFontOfSize:16]; - - newPhoneCell.mxkTextField.userInteractionEnabled = YES; - newPhoneCell.mxkTextField.keyboardType = UIKeyboardTypePhonePad; - newPhoneCell.mxkTextField.autocorrectionType = UITextAutocorrectionTypeNo; - newPhoneCell.mxkTextField.spellCheckingType = UITextSpellCheckingTypeNo; - newPhoneCell.mxkTextField.delegate = self; - newPhoneCell.mxkTextField.accessibilityIdentifier=@"SettingsVCAddPhoneTextField"; - - [newPhoneCell.mxkTextField removeTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; - [newPhoneCell.mxkTextField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; - - [newPhoneCell.mxkTextField removeTarget:self action:@selector(textFieldDidEnd:) forControlEvents:UIControlEventEditingDidEnd]; - [newPhoneCell.mxkTextField addTarget:self action:@selector(textFieldDidEnd:) forControlEvents:UIControlEventEditingDidEnd]; - - newPhoneCell.mxkTextField.tag = row; - - // When displaying the textfield the 1st time, open the keyboard - if (!newPhoneNumberCell) - { - NSString *countryCode = [MXKAppSettings standardAppSettings].phonebookCountryCode; - if (!countryCode) - { - // If none, consider the preferred locale - NSLocale *local = [[NSLocale alloc] initWithLocaleIdentifier:[[NSBundle mainBundle] preferredLocalizations][0]]; - if ([local respondsToSelector:@selector(countryCode)]) - { - countryCode = local.countryCode; - } - - if (!countryCode) - { - countryCode = @"GB"; - } - } - newPhoneCell.isoCountryCode = countryCode; - newPhoneCell.mxkTextField.text = nil; - - newPhoneNumberCell = newPhoneCell; - - [self editNewPhoneNumberTextField]; - } - else - { - newPhoneCell.isoCountryCode = newPhoneNumberCell.isoCountryCode; - newPhoneCell.mxkTextField.text = newPhoneNumberCell.mxkTextField.text; - - newPhoneNumberCell = newPhoneCell; - } - - UIImage *accessoryViewImage = [MXKTools paintImage:[UIImage imageNamed:@"plus_icon"] withColor:ThemeService.shared.theme.tintColor]; - newPhoneCell.accessoryView = [[UIImageView alloc] initWithImage:accessoryViewImage]; - - cell = newPhoneCell; + // Fix https://github.com/vector-im/riot-ios/issues/1354 + exportKeysBtnCell.mxkButton.titleLabel.text = nil; } + + NSString *btnTitle = NSLocalizedStringFromTable(@"settings_crypto_export", @"Vector", nil); + [exportKeysBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; + [exportKeysBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; + [exportKeysBtnCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; + exportKeysBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; + + [exportKeysBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; + [exportKeysBtnCell.mxkButton addTarget:self action:@selector(exportEncryptionKeys:) forControlEvents:UIControlEventTouchUpInside]; + exportKeysBtnCell.mxkButton.accessibilityIdentifier = nil; + + cell = exportKeysBtnCell; } - else if (row == userSettingsThreePidsInformation) - { - MXKTableViewCell *threePidsInformationCell = [self getDefaultTableViewCell:self.tableView]; - - NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"settings_three_pids_management_information_part1", @"Vector", nil) attributes:@{NSForegroundColorAttributeName: ThemeService.shared.theme.textPrimaryColor}]; - [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"settings_three_pids_management_information_part2", @"Vector", nil) attributes:@{NSForegroundColorAttributeName: ThemeService.shared.theme.tintColor}]]; - [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"settings_three_pids_management_information_part3", @"Vector", nil) attributes:@{NSForegroundColorAttributeName: ThemeService.shared.theme.textPrimaryColor}]]; - - threePidsInformationCell.textLabel.attributedText = attributedString; - threePidsInformationCell.textLabel.numberOfLines = 0; - - threePidsInformationCell.selectionStyle = UITableViewCellSelectionStyleNone; - - cell = threePidsInformationCell; - } - else if (row == userSettingsChangePasswordIndex) - { - MXKTableViewCellWithLabelAndTextField *passwordCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; - - passwordCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_change_password", @"Vector", nil); - passwordCell.mxkTextField.text = @"*********"; - passwordCell.mxkTextField.userInteractionEnabled = NO; - passwordCell.mxkLabel.accessibilityIdentifier=@"SettingsVCChangePwdStaticText"; - - cell = passwordCell; - } - else if (row == userSettingsNightModeSepIndex) + } + else if (section == SETTINGS_SECTION_KEYBACKUP_INDEX) + { + cell = [keyBackupSection cellForRowAtRow:row]; + } + + return cell; +} + +- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section +{ + if (section == SETTINGS_SECTION_DEVICES_INDEX) + { + // Check whether this section is visible + if (devicesArray.count > 0) { - UITableViewCell *sepCell = [[UITableViewCell alloc] init]; - sepCell.backgroundColor = ThemeService.shared.theme.headerBackgroundColor; - - cell = sepCell; + return NSLocalizedStringFromTable(@"settings_devices", @"Vector", nil); } - else if (row == userSettingsNightModeIndex) + } + else if (section == SETTINGS_SECTION_CRYPTOGRAPHY_INDEX) + { + // Check whether this section is visible + if (self.mainSession.crypto) { - MXKTableViewCellWithLabelAndTextField *nightModeCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; - - nightModeCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_night_mode", @"Vector", nil); - nightModeCell.mxkTextField.userInteractionEnabled = NO; - nightModeCell.mxkTextField.text = NSLocalizedStringFromTable(@"off", @"Vector", nil); - nightModeCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; - cell = nightModeCell; + return NSLocalizedStringFromTable(@"settings_cryptography", @"Vector", nil); } } - else if (section == SETTINGS_SECTION_NOTIFICATIONS_SETTINGS_INDEX) + else if (section == SETTINGS_SECTION_KEYBACKUP_INDEX) { - if (row == NOTIFICATION_SETTINGS_ENABLE_PUSH_INDEX) + // Check whether this section is visible + if (self.mainSession.crypto) { - MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - - labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_enable_push_notif", @"Vector", nil); - labelAndSwitchCell.mxkSwitch.on = account.isPushKitNotificationActive; - labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; - labelAndSwitchCell.mxkSwitch.enabled = YES; - [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(togglePushNotifications:) forControlEvents:UIControlEventTouchUpInside]; - - cell = labelAndSwitchCell; - } - else if (row == NOTIFICATION_SETTINGS_SHOW_DECODED_CONTENT) - { - MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - - labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_show_decrypted_content", @"Vector", nil); - labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.showDecryptedContentInNotifications; - labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; - labelAndSwitchCell.mxkSwitch.enabled = account.isPushKitNotificationActive; - [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleShowDecodedContent:) forControlEvents:UIControlEventTouchUpInside]; - - - cell = labelAndSwitchCell; - } - else if (row == NOTIFICATION_SETTINGS_GLOBAL_SETTINGS_INDEX) - { - MXKTableViewCell *globalInfoCell = [self getDefaultTableViewCell:tableView]; - - NSString *appDisplayName = [[NSBundle mainBundle] infoDictionary][@"CFBundleDisplayName"]; - - globalInfoCell.textLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"settings_global_settings_info", @"Vector", nil), appDisplayName]; - globalInfoCell.textLabel.numberOfLines = 0; - - globalInfoCell.selectionStyle = UITableViewCellSelectionStyleNone; - - cell = globalInfoCell; - } - else if (row == NOTIFICATION_SETTINGS_PIN_MISSED_NOTIFICATIONS_INDEX) - { - MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - - labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_pin_rooms_with_missed_notif", @"Vector", nil); - labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.pinRoomsWithMissedNotificationsOnHome; - labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; - labelAndSwitchCell.mxkSwitch.enabled = YES; - [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(togglePinRoomsWithMissedNotif:) forControlEvents:UIControlEventTouchUpInside]; - - cell = labelAndSwitchCell; - } - else if (row == NOTIFICATION_SETTINGS_PIN_UNREAD_INDEX) - { - MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - - labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_pin_rooms_with_unread", @"Vector", nil); - labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.pinRoomsWithUnreadMessagesOnHome; - labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; - labelAndSwitchCell.mxkSwitch.enabled = YES; - [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(togglePinRoomsWithUnread:) forControlEvents:UIControlEventTouchUpInside]; - - cell = labelAndSwitchCell; - } - } - else if (section == SETTINGS_SECTION_CALLS_INDEX) - { - if (row == CALLS_ENABLE_CALLKIT_INDEX) - { - MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_enable_callkit", @"Vector", nil); - labelAndSwitchCell.mxkSwitch.on = [MXKAppSettings standardAppSettings].isCallKitEnabled; - labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; - labelAndSwitchCell.mxkSwitch.enabled = YES; - [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleCallKit:) forControlEvents:UIControlEventTouchUpInside]; - - if (![MXCallKitAdapter callKitAvailable]) - { - labelAndSwitchCell.mxkSwitch.on = NO; - labelAndSwitchCell.mxkSwitch.enabled = NO; - labelAndSwitchCell.mxkLabel.enabled = NO; - } - - cell = labelAndSwitchCell; - } - else if (row == CALLS_CALLKIT_DESCRIPTION_INDEX) - { - MXKTableViewCell *globalInfoCell = [self getDefaultTableViewCell:tableView]; - globalInfoCell.textLabel.text = NSLocalizedStringFromTable(@"settings_callkit_info", @"Vector", nil); - globalInfoCell.textLabel.numberOfLines = 0; - globalInfoCell.selectionStyle = UITableViewCellSelectionStyleNone; - - if (![MXCallKitAdapter callKitAvailable]) - { - globalInfoCell.textLabel.enabled = NO; - } - - cell = globalInfoCell; - } - else if (row == CALLS_ENABLE_STUN_SERVER_FALLBACK_INDEX) - { - MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_calls_stun_server_fallback_button", @"Vector", nil); - labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.allowStunServerFallback; - labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; - labelAndSwitchCell.mxkSwitch.enabled = YES; - [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleStunServerFallback:) forControlEvents:UIControlEventTouchUpInside]; - - cell = labelAndSwitchCell; - } - else if (row == CALLS_STUN_SERVER_FALLBACK_DESCRIPTION_INDEX) - { - NSString *stunFallbackHost = RiotSettings.shared.stunServerFallback; - // Remove "stun:" - stunFallbackHost = [stunFallbackHost componentsSeparatedByString:@":"].lastObject; - - MXKTableViewCell *globalInfoCell = [self getDefaultTableViewCell:tableView]; - globalInfoCell.textLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"settings_calls_stun_server_fallback_description", @"Vector", nil), stunFallbackHost]; - globalInfoCell.textLabel.numberOfLines = 0; - globalInfoCell.selectionStyle = UITableViewCellSelectionStyleNone; - - cell = globalInfoCell; - } - } - else if (section == SETTINGS_SECTION_DISCOVERY_INDEX) - { - cell = [self.settingsDiscoveryTableViewSection cellForRowAtRow:row]; - } - else if (section == SETTINGS_SECTION_IDENTITY_SERVER_INDEX) - { - switch (row) - { - case IDENTITY_SERVER_INDEX: - { - MXKTableViewCell *isCell = [self getDefaultTableViewCell:tableView]; - - if (account.mxSession.identityService.identityServer) - { - isCell.textLabel.text = account.mxSession.identityService.identityServer; - } - else - { - isCell.textLabel.text = NSLocalizedStringFromTable(@"settings_identity_server_no_is", @"Vector", nil); - } - isCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; - cell = isCell; - break; - } - - case IDENTITY_SERVER_DESCRIPTION_INDEX: - { - MXKTableViewCell *descriptionCell = [self getDefaultTableViewCell:tableView]; - - if (account.mxSession.identityService.identityServer) - { - descriptionCell.textLabel.text = NSLocalizedStringFromTable(@"settings_identity_server_description", @"Vector", nil); - } - else - { - descriptionCell.textLabel.text = NSLocalizedStringFromTable(@"settings_identity_server_no_is_description", @"Vector", nil); - } - descriptionCell.textLabel.numberOfLines = 0; - descriptionCell.selectionStyle = UITableViewCellSelectionStyleNone; - - cell = descriptionCell; - break; - } - - default: - break; - } - } - else if (section == SETTINGS_SECTION_INTEGRATIONS_INDEX) - { - switch (row) { - case INTEGRATIONS_INDEX: - { - RiotSharedSettings *sharedSettings = [[RiotSharedSettings alloc] initWithSession:session]; - - MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_integrations_allow_button", @"Vector", nil); - labelAndSwitchCell.mxkSwitch.on = sharedSettings.hasIntegrationProvisioningEnabled; - labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; - labelAndSwitchCell.mxkSwitch.enabled = YES; - [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleAllowIntegrations:) forControlEvents:UIControlEventTouchUpInside]; - - cell = labelAndSwitchCell; - break; - } - - case INTEGRATIONS_DESCRIPTION_INDEX: - { - MXKTableViewCell *descriptionCell = [self getDefaultTableViewCell:tableView]; - - NSString *integrationManager = [WidgetManager.sharedManager configForUser:session.myUser.userId].apiUrl; - NSString *integrationManagerDomain = [NSURL URLWithString:integrationManager].host; - - NSString *description = [NSString stringWithFormat:NSLocalizedStringFromTable(@"settings_integrations_allow_description", @"Vector", nil), integrationManagerDomain]; - descriptionCell.textLabel.text = description; - descriptionCell.textLabel.numberOfLines = 0; - descriptionCell.selectionStyle = UITableViewCellSelectionStyleNone; - - cell = descriptionCell; - break; - } - - default: - break; - } - } - else if (section == SETTINGS_SECTION_USER_INTERFACE_INDEX) - { - if (row == USER_INTERFACE_LANGUAGE_INDEX) - { - cell = [tableView dequeueReusableCellWithIdentifier:kSettingsViewControllerPhoneBookCountryCellId2]; - if (!cell) - { - cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:kSettingsViewControllerPhoneBookCountryCellId2]; - } - - NSString *language = [NSBundle mxk_language]; - if (!language) - { - language = [MXKLanguagePickerViewController defaultLanguage]; - } - NSString *languageDescription = [MXKLanguagePickerViewController languageDescription:language]; - - // Capitalise the description in the language locale - NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:language]; - languageDescription = [languageDescription capitalizedStringWithLocale:locale]; - - cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; - - cell.textLabel.text = NSLocalizedStringFromTable(@"settings_ui_language", @"Vector", nil); - cell.detailTextLabel.text = languageDescription; - - cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; - cell.selectionStyle = UITableViewCellSelectionStyleDefault; - } - else if (row == USER_INTERFACE_THEME_INDEX) - { - cell = [tableView dequeueReusableCellWithIdentifier:kSettingsViewControllerPhoneBookCountryCellId2]; - if (!cell) - { - cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:kSettingsViewControllerPhoneBookCountryCellId2]; - } - - NSString *theme = RiotSettings.shared.userInterfaceTheme; - - if (!theme) - { - if (@available(iOS 11.0, *)) - { - // "auto" is used the default value from iOS 11 - theme = @"auto"; - } - else - { - // Use "light" for older version - theme = @"light"; - } - } - - theme = [NSString stringWithFormat:@"settings_ui_theme_%@", theme]; - NSString *i18nTheme = NSLocalizedStringFromTable(theme, - @"Vector", - nil); - - cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; - - cell.textLabel.text = NSLocalizedStringFromTable(@"settings_ui_theme", @"Vector", nil); - cell.detailTextLabel.text = i18nTheme; - - cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; - cell.selectionStyle = UITableViewCellSelectionStyleDefault; - } - } - else if (section == SETTINGS_SECTION_IGNORED_USERS_INDEX) - { - MXKTableViewCell *ignoredUserCell = [self getDefaultTableViewCell:tableView]; - - NSString *ignoredUserId; - if (indexPath.row < session.ignoredUsers.count) - { - ignoredUserId = session.ignoredUsers[indexPath.row]; - } - ignoredUserCell.textLabel.text = ignoredUserId; - - cell = ignoredUserCell; - } - else if (section == SETTINGS_SECTION_CONTACTS_INDEX) - { - if (row == localContactsSyncIndex) - { - MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - - labelAndSwitchCell.mxkLabel.numberOfLines = 0; - labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_contacts_discover_matrix_users", @"Vector", nil); - labelAndSwitchCell.mxkSwitch.on = [MXKAppSettings standardAppSettings].syncLocalContacts; - labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; - labelAndSwitchCell.mxkSwitch.enabled = YES; - [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleLocalContactsSync:) forControlEvents:UIControlEventTouchUpInside]; - - cell = labelAndSwitchCell; - } - else if (row == localContactsPhoneBookCountryIndex) - { - cell = [tableView dequeueReusableCellWithIdentifier:kSettingsViewControllerPhoneBookCountryCellId2]; - if (!cell) - { - cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:kSettingsViewControllerPhoneBookCountryCellId2]; - } - - NSString* countryCode = [[MXKAppSettings standardAppSettings] phonebookCountryCode]; - NSLocale *local = [[NSLocale alloc] initWithLocaleIdentifier:[[NSBundle mainBundle] preferredLocalizations][0]]; - NSString *countryName = [local displayNameForKey:NSLocaleCountryCode value:countryCode]; - - cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; - - cell.textLabel.text = NSLocalizedStringFromTable(@"settings_contacts_phonebook_country", @"Vector", nil); - cell.detailTextLabel.text = countryName; - - cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; - cell.selectionStyle = UITableViewCellSelectionStyleDefault; - } - } - else if (section == SETTINGS_SECTION_ADVANCED_INDEX) - { - MXKTableViewCellWithTextView *configCell = [self textViewCellForTableView:tableView atIndexPath:indexPath]; - - NSString *configFormat = [NSString stringWithFormat:@"%@\n%@\n%@", [NSBundle mxk_localizedStringForKey:@"settings_config_user_id"], [NSBundle mxk_localizedStringForKey:@"settings_config_home_server"], [NSBundle mxk_localizedStringForKey:@"settings_config_identity_server"]]; - - configCell.mxkTextView.text =[NSString stringWithFormat:configFormat, account.mxCredentials.userId, account.mxCredentials.homeServer, account.identityServerURL]; - configCell.mxkTextView.accessibilityIdentifier=@"SettingsVCConfigStaticText"; - - cell = configCell; - } - else if (section == SETTINGS_SECTION_OTHER_INDEX) - { - if (row == OTHER_VERSION_INDEX) - { - MXKTableViewCell *versionCell = [self getDefaultTableViewCell:tableView]; - - NSString* appVersion = [AppDelegate theDelegate].appVersion; - NSString* build = [AppDelegate theDelegate].build; - - versionCell.textLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"settings_version", @"Vector", nil), [NSString stringWithFormat:@"%@ %@", appVersion, build]]; - - versionCell.selectionStyle = UITableViewCellSelectionStyleNone; - - cell = versionCell; - } - else if (row == OTHER_OLM_VERSION_INDEX) - { - MXKTableViewCell *versionCell = [self getDefaultTableViewCell:tableView]; - - versionCell.textLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"settings_olm_version", @"Vector", nil), [OLMKit versionString]]; - - versionCell.selectionStyle = UITableViewCellSelectionStyleNone; - - cell = versionCell; - } - else if (row == OTHER_TERM_CONDITIONS_INDEX) - { - MXKTableViewCell *termAndConditionCell = [self getDefaultTableViewCell:tableView]; - - termAndConditionCell.textLabel.text = NSLocalizedStringFromTable(@"settings_term_conditions", @"Vector", nil); - - termAndConditionCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; - - cell = termAndConditionCell; - } - else if (row == OTHER_COPYRIGHT_INDEX) - { - MXKTableViewCell *copyrightCell = [self getDefaultTableViewCell:tableView]; - - copyrightCell.textLabel.text = NSLocalizedStringFromTable(@"settings_copyright", @"Vector", nil); - - copyrightCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; - - cell = copyrightCell; - } - else if (row == OTHER_PRIVACY_INDEX) - { - MXKTableViewCell *privacyPolicyCell = [self getDefaultTableViewCell:tableView]; - - privacyPolicyCell.textLabel.text = NSLocalizedStringFromTable(@"settings_privacy_policy", @"Vector", nil); - - privacyPolicyCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; - - cell = privacyPolicyCell; - } - else if (row == OTHER_THIRD_PARTY_INDEX) - { - MXKTableViewCell *thirdPartyCell = [self getDefaultTableViewCell:tableView]; - - thirdPartyCell.textLabel.text = NSLocalizedStringFromTable(@"settings_third_party_notices", @"Vector", nil); - - thirdPartyCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; - - cell = thirdPartyCell; - } - else if (row == OTHER_CRASH_REPORT_INDEX) - { - MXKTableViewCellWithLabelAndSwitch* sendCrashReportCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - - sendCrashReportCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_send_crash_report", @"Vector", nil); - sendCrashReportCell.mxkSwitch.on = RiotSettings.shared.enableCrashReport; - sendCrashReportCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; - sendCrashReportCell.mxkSwitch.enabled = YES; - [sendCrashReportCell.mxkSwitch addTarget:self action:@selector(toggleSendCrashReport:) forControlEvents:UIControlEventTouchUpInside]; - - cell = sendCrashReportCell; - } - else if (row == OTHER_ENABLE_RAGESHAKE_INDEX) - { - MXKTableViewCellWithLabelAndSwitch* enableRageShakeCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - - enableRageShakeCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_enable_rageshake", @"Vector", nil); - enableRageShakeCell.mxkSwitch.on = RiotSettings.shared.enableRageShake; - enableRageShakeCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; - enableRageShakeCell.mxkSwitch.enabled = YES; - [enableRageShakeCell.mxkSwitch addTarget:self action:@selector(toggleEnableRageShake:) forControlEvents:UIControlEventTouchUpInside]; - - cell = enableRageShakeCell; - } - else if (row == OTHER_MARK_ALL_AS_READ_INDEX) - { - MXKTableViewCellWithButton *markAllBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; - if (!markAllBtnCell) - { - markAllBtnCell = [[MXKTableViewCellWithButton alloc] init]; - } - else - { - // Fix https://github.com/vector-im/riot-ios/issues/1354 - markAllBtnCell.mxkButton.titleLabel.text = nil; - } - - NSString *btnTitle = NSLocalizedStringFromTable(@"settings_mark_all_as_read", @"Vector", nil); - [markAllBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; - [markAllBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; - [markAllBtnCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; - markAllBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; - - [markAllBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; - [markAllBtnCell.mxkButton addTarget:self action:@selector(markAllAsRead:) forControlEvents:UIControlEventTouchUpInside]; - markAllBtnCell.mxkButton.accessibilityIdentifier = nil; - - cell = markAllBtnCell; - } - else if (row == OTHER_CLEAR_CACHE_INDEX) - { - MXKTableViewCellWithButton *clearCacheBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; - if (!clearCacheBtnCell) - { - clearCacheBtnCell = [[MXKTableViewCellWithButton alloc] init]; - } - else - { - // Fix https://github.com/vector-im/riot-ios/issues/1354 - clearCacheBtnCell.mxkButton.titleLabel.text = nil; - } - - NSString *btnTitle = NSLocalizedStringFromTable(@"settings_clear_cache", @"Vector", nil); - [clearCacheBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; - [clearCacheBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; - [clearCacheBtnCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; - clearCacheBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; - - [clearCacheBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; - [clearCacheBtnCell.mxkButton addTarget:self action:@selector(clearCache:) forControlEvents:UIControlEventTouchUpInside]; - clearCacheBtnCell.mxkButton.accessibilityIdentifier = nil; - - cell = clearCacheBtnCell; - } - else if (row == OTHER_REPORT_BUG_INDEX) - { - MXKTableViewCellWithButton *reportBugBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; - if (!reportBugBtnCell) - { - reportBugBtnCell = [[MXKTableViewCellWithButton alloc] init]; - } - else - { - // Fix https://github.com/vector-im/riot-ios/issues/1354 - reportBugBtnCell.mxkButton.titleLabel.text = nil; - } - - NSString *btnTitle = NSLocalizedStringFromTable(@"settings_report_bug", @"Vector", nil); - [reportBugBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; - [reportBugBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; - [reportBugBtnCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; - reportBugBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; - - [reportBugBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; - [reportBugBtnCell.mxkButton addTarget:self action:@selector(reportBug:) forControlEvents:UIControlEventTouchUpInside]; - reportBugBtnCell.mxkButton.accessibilityIdentifier = nil; - - cell = reportBugBtnCell; - } - } - else if (section == SETTINGS_SECTION_LABS_INDEX) - { - if (row == LABS_USE_ROOM_MEMBERS_LAZY_LOADING_INDEX) - { - MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - - labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_labs_room_members_lazy_loading", @"Vector", nil); - - MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; - labelAndSwitchCell.mxkSwitch.on = account.mxSession.syncWithLazyLoadOfRoomMembers; - labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; - - [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleSyncWithLazyLoadOfRoomMembers:) forControlEvents:UIControlEventTouchUpInside]; - - cell = labelAndSwitchCell; - } - else if (row == LABS_USE_JITSI_WIDGET_INDEX) - { - MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - - labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_labs_create_conference_with_jitsi", @"Vector", nil); - labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.createConferenceCallsWithJitsi; - labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; - - [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleJitsiForConference:) forControlEvents:UIControlEventTouchUpInside]; - - cell = labelAndSwitchCell; - } - else if (row == LABS_CROSS_SIGNING_INDEX) - { - MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - - labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_labs_enable_cross_signing", @"Vector", nil); - labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.enableCrossSigning; - labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; - labelAndSwitchCell.mxkSwitch.enabled = YES; - - [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleLabsCrossSigning:) forControlEvents:UIControlEventTouchUpInside]; - - cell = labelAndSwitchCell; - } - } - else if (section == SETTINGS_SECTION_FLAIR_INDEX) - { - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:groupsDataSource.joinedGroupsSection]; - cell = [groupsDataSource tableView:tableView cellForRowAtIndexPath:indexPath]; - - if ([cell isKindOfClass:GroupTableViewCellWithSwitch.class]) - { - GroupTableViewCellWithSwitch* groupWithSwitchCell = (GroupTableViewCellWithSwitch*)cell; - id groupCellData = [groupsDataSource cellDataAtIndex:indexPath]; - - // Display the groupId in the description label, except if the group has no name - if (![groupWithSwitchCell.groupName.text isEqualToString:groupCellData.group.groupId]) - { - groupWithSwitchCell.groupDescription.hidden = NO; - groupWithSwitchCell.groupDescription.text = groupCellData.group.groupId; - } - - // Update the toogle button - groupWithSwitchCell.toggleButton.on = groupCellData.group.summary.user.isPublicised; - groupWithSwitchCell.toggleButton.enabled = YES; - groupWithSwitchCell.toggleButton.tag = row; - - [groupWithSwitchCell.toggleButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; - [groupWithSwitchCell.toggleButton addTarget:self action:@selector(toggleCommunityFlair:) forControlEvents:UIControlEventTouchUpInside]; - } - } - else if (section == SETTINGS_SECTION_DEVICES_INDEX) - { - if (row == DEVICES_DESCRIPTION_INDEX) - { - MXKTableViewCell *descriptionCell = [self getDefaultTableViewCell:tableView]; - descriptionCell.textLabel.text = NSLocalizedStringFromTable(@"settings_devices_description", @"Vector", nil); - descriptionCell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; - descriptionCell.textLabel.font = [UIFont systemFontOfSize:15]; - descriptionCell.textLabel.numberOfLines = 0; - descriptionCell.contentView.backgroundColor = ThemeService.shared.theme.headerBackgroundColor; - descriptionCell.selectionStyle = UITableViewCellSelectionStyleNone; - - cell = descriptionCell; - } - else - { - NSUInteger deviceIndex = row - 1; - - MXKTableViewCell *deviceCell = [self getDefaultTableViewCell:tableView]; - if (deviceIndex < devicesArray.count) - { - NSString *name = devicesArray[deviceIndex].displayName; - NSString *deviceId = devicesArray[deviceIndex].deviceId; - deviceCell.textLabel.text = (name.length ? [NSString stringWithFormat:@"%@ (%@)", name, deviceId] : [NSString stringWithFormat:@"(%@)", deviceId]); - deviceCell.textLabel.numberOfLines = 0; - - if ([deviceId isEqualToString:self.mainSession.matrixRestClient.credentials.deviceId]) - { - deviceCell.textLabel.font = [UIFont boldSystemFontOfSize:17]; - } - } - - cell = deviceCell; - } - - } - else if (section == SETTINGS_SECTION_CRYPTOGRAPHY_INDEX) - { - if (row == CRYPTOGRAPHY_INFO_INDEX) - { - MXKTableViewCellWithTextView *cryptoCell = [self textViewCellForTableView:tableView atIndexPath:indexPath]; - - cryptoCell.mxkTextView.attributedText = [self cryptographyInformation]; - - cell = cryptoCell; - } - else if (row == CRYPTOGRAPHY_BLACKLIST_UNVERIFIED_DEVICES_INDEX) - { - MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - - labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_crypto_blacklist_unverified_devices", @"Vector", nil); - labelAndSwitchCell.mxkSwitch.on = account.mxSession.crypto.globalBlacklistUnverifiedDevices; - labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; - labelAndSwitchCell.mxkSwitch.enabled = YES; - [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleBlacklistUnverifiedDevices:) forControlEvents:UIControlEventTouchUpInside]; - - cell = labelAndSwitchCell; - } - else if (row == CRYPTOGRAPHY_EXPORT_INDEX) - { - MXKTableViewCellWithButton *exportKeysBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; - if (!exportKeysBtnCell) - { - exportKeysBtnCell = [[MXKTableViewCellWithButton alloc] init]; - } - else - { - // Fix https://github.com/vector-im/riot-ios/issues/1354 - exportKeysBtnCell.mxkButton.titleLabel.text = nil; - } - - NSString *btnTitle = NSLocalizedStringFromTable(@"settings_crypto_export", @"Vector", nil); - [exportKeysBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; - [exportKeysBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; - [exportKeysBtnCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; - exportKeysBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; - - [exportKeysBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; - [exportKeysBtnCell.mxkButton addTarget:self action:@selector(exportEncryptionKeys:) forControlEvents:UIControlEventTouchUpInside]; - exportKeysBtnCell.mxkButton.accessibilityIdentifier = nil; - - cell = exportKeysBtnCell; - } - } - else if (section == SETTINGS_SECTION_KEYBACKUP_INDEX) - { - cell = [keyBackupSection cellForRowAtRow:row]; - } - else if (section == SETTINGS_SECTION_DEACTIVATE_ACCOUNT_INDEX) - { - MXKTableViewCellWithButton *deactivateAccountBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; - - if (!deactivateAccountBtnCell) - { - deactivateAccountBtnCell = [[MXKTableViewCellWithButton alloc] init]; - } - else - { - // Fix https://github.com/vector-im/riot-ios/issues/1354 - deactivateAccountBtnCell.mxkButton.titleLabel.text = nil; - } - - NSString *btnTitle = NSLocalizedStringFromTable(@"settings_deactivate_my_account", @"Vector", nil); - [deactivateAccountBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; - [deactivateAccountBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; - [deactivateAccountBtnCell.mxkButton setTintColor:ThemeService.shared.theme.warningColor]; - deactivateAccountBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; - - [deactivateAccountBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; - [deactivateAccountBtnCell.mxkButton addTarget:self action:@selector(deactivateAccountAction) forControlEvents:UIControlEventTouchUpInside]; - deactivateAccountBtnCell.mxkButton.accessibilityIdentifier = nil; - - cell = deactivateAccountBtnCell; - } - - return cell; -} - -- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section -{ - if (section == SETTINGS_SECTION_USER_SETTINGS_INDEX) - { - return NSLocalizedStringFromTable(@"settings_user_settings", @"Vector", nil); - } - else if (section == SETTINGS_SECTION_NOTIFICATIONS_SETTINGS_INDEX) - { - return NSLocalizedStringFromTable(@"settings_notifications_settings", @"Vector", nil); - } - else if (section == SETTINGS_SECTION_CALLS_INDEX) - { - return NSLocalizedStringFromTable(@"settings_calls_settings", @"Vector", nil); - } - else if (section == SETTINGS_SECTION_DISCOVERY_INDEX) - { - return NSLocalizedStringFromTable(@"settings_discovery_settings", @"Vector", nil); - } - else if (section == SETTINGS_SECTION_IDENTITY_SERVER_INDEX) - { - return NSLocalizedStringFromTable(@"settings_identity_server_settings", @"Vector", nil); - } - else if (section == SETTINGS_SECTION_INTEGRATIONS_INDEX) - { - return NSLocalizedStringFromTable(@"settings_integrations", @"Vector", nil); - } - else if (section == SETTINGS_SECTION_USER_INTERFACE_INDEX) - { - return NSLocalizedStringFromTable(@"settings_user_interface", @"Vector", nil); - } - else if (section == SETTINGS_SECTION_IGNORED_USERS_INDEX) - { - // Check whether this section is visible - if ([AppDelegate theDelegate].mxSessions.count > 0) - { - MXSession* session = [AppDelegate theDelegate].mxSessions[0]; - if (session.ignoredUsers.count) - { - return NSLocalizedStringFromTable(@"settings_ignored_users", @"Vector", nil); - } - } - } - else if (section == SETTINGS_SECTION_CONTACTS_INDEX) - { - return NSLocalizedStringFromTable(@"settings_contacts", @"Vector", nil); - } - else if (section == SETTINGS_SECTION_ADVANCED_INDEX) - { - return NSLocalizedStringFromTable(@"settings_advanced", @"Vector", nil); - } - else if (section == SETTINGS_SECTION_OTHER_INDEX) - { - return NSLocalizedStringFromTable(@"settings_other", @"Vector", nil); - } - else if (section == SETTINGS_SECTION_LABS_INDEX) - { - return NSLocalizedStringFromTable(@"settings_labs", @"Vector", nil); - } - else if (section == SETTINGS_SECTION_FLAIR_INDEX) - { - // Check whether this section is visible - if (groupsDataSource.joinedGroupsSection != -1) - { - return NSLocalizedStringFromTable(@"settings_flair", @"Vector", nil); - } - } - else if (section == SETTINGS_SECTION_DEVICES_INDEX) - { - // Check whether this section is visible - if (devicesArray.count > 0) - { - return NSLocalizedStringFromTable(@"settings_devices", @"Vector", nil); - } - } - else if (section == SETTINGS_SECTION_CRYPTOGRAPHY_INDEX) - { - // Check whether this section is visible - if (self.mainSession.crypto) - { - return NSLocalizedStringFromTable(@"settings_cryptography", @"Vector", nil); - } - } - else if (section == SETTINGS_SECTION_KEYBACKUP_INDEX) - { - // Check whether this section is visible - if (self.mainSession.crypto) - { - return NSLocalizedStringFromTable(@"settings_key_backup", @"Vector", nil); - } - } - else if (section == SETTINGS_SECTION_DEACTIVATE_ACCOUNT_INDEX) - { - return NSLocalizedStringFromTable(@"settings_deactivate_my_account", @"Vector", nil); - } - - return nil; -} - -- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section -{ - if ([view isKindOfClass:UITableViewHeaderFooterView.class]) - { - // Customize label style - UITableViewHeaderFooterView *tableViewHeaderFooterView = (UITableViewHeaderFooterView*)view; - tableViewHeaderFooterView.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; - tableViewHeaderFooterView.textLabel.font = [UIFont systemFontOfSize:15]; - } -} - -- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath -{ - if (indexPath.section == SETTINGS_SECTION_USER_SETTINGS_INDEX) - { - NSInteger row = indexPath.row; - if ((userSettingsEmailStartIndex <= row && row < userSettingsNewEmailIndex) || - (userSettingsPhoneStartIndex <= row && row < userSettingsNewPhoneIndex)) - { - return YES; - } - } - return NO; -} - -- (void)tableView:(UITableView*)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath*)indexPath -{ - // iOS8 requires this method to enable editing (see editActionsForRowAtIndexPath). -} - -#pragma mark - UITableView delegate - -- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath; -{ - cell.backgroundColor = ThemeService.shared.theme.backgroundColor; - - if (cell.selectionStyle != UITableViewCellSelectionStyleNone) - { - // Update the selected background view - if (ThemeService.shared.theme.selectedBackgroundColor) - { - cell.selectedBackgroundView = [[UIView alloc] init]; - cell.selectedBackgroundView.backgroundColor = ThemeService.shared.theme.selectedBackgroundColor; - } - else - { - if (tableView.style == UITableViewStylePlain) - { - cell.selectedBackgroundView = nil; - } - else - { - cell.selectedBackgroundView.backgroundColor = nil; - } - } - } -} - -- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section -{ - if (section == SETTINGS_SECTION_IGNORED_USERS_INDEX) - { - if ([AppDelegate theDelegate].mxSessions.count > 0) - { - MXSession* session = [AppDelegate theDelegate].mxSessions[0]; - if (session.ignoredUsers.count == 0) - { - // Hide this section - return SECTION_TITLE_PADDING_WHEN_HIDDEN; - } - } - } - else if (section == SETTINGS_SECTION_FLAIR_INDEX) - { - if (groupsDataSource.joinedGroupsSection == -1) - { - return SECTION_TITLE_PADDING_WHEN_HIDDEN; - } - } - - return 24; -} - -- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section -{ - if (section == SETTINGS_SECTION_IGNORED_USERS_INDEX) - { - if ([AppDelegate theDelegate].mxSessions.count > 0) - { - MXSession* session = [AppDelegate theDelegate].mxSessions[0]; - if (session.ignoredUsers.count == 0) - { - // Hide this section - return SECTION_TITLE_PADDING_WHEN_HIDDEN; - } - } - } - else if (section == SETTINGS_SECTION_FLAIR_INDEX) - { - if (groupsDataSource.joinedGroupsSection == -1) - { - return SECTION_TITLE_PADDING_WHEN_HIDDEN; - } - } - - return 24; -} - -- (NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath -{ - NSMutableArray* actions; - - // Add the swipe to delete user's email or phone number - if (indexPath.section == SETTINGS_SECTION_USER_SETTINGS_INDEX) - { - NSInteger row = indexPath.row; - if ((userSettingsEmailStartIndex <= row && row < userSettingsNewEmailIndex) || - (userSettingsPhoneStartIndex <= row && row < userSettingsNewPhoneIndex)) - { - actions = [[NSMutableArray alloc] init]; - - UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; - CGFloat cellHeight = cell ? cell.frame.size.height : 50; - - // Patch: Force the width of the button by adding whitespace characters into the title string. - UITableViewRowAction *leaveAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive title:@" " handler:^(UITableViewRowAction *action, NSIndexPath *indexPath){ - - [self onRemove3PID:indexPath]; - - }]; - - leaveAction.backgroundColor = [MXKTools convertImageToPatternColor:@"remove_icon_pink" backgroundColor:ThemeService.shared.theme.headerBackgroundColor patternSize:CGSizeMake(50, cellHeight) resourceSize:CGSizeMake(24, 24)]; - [actions insertObject:leaveAction atIndex:0]; - } - } - - return actions; -} - -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath -{ - if (self.tableView == tableView) - { - NSInteger section = indexPath.section; - NSInteger row = indexPath.row; - - if (section == SETTINGS_SECTION_USER_INTERFACE_INDEX) - { - if (row == USER_INTERFACE_LANGUAGE_INDEX) - { - // Display the language picker - LanguagePickerViewController *languagePickerViewController = [LanguagePickerViewController languagePickerViewController]; - languagePickerViewController.selectedLanguage = [NSBundle mxk_language]; - languagePickerViewController.delegate = self; - [self pushViewController:languagePickerViewController]; - } - else if (row == USER_INTERFACE_THEME_INDEX) - { - [self showThemePicker]; - } - } - else if (section == SETTINGS_SECTION_USER_SETTINGS_INDEX && row == userSettingsThreePidsInformation) - { - NSIndexPath *discoveryIndexPath = [NSIndexPath indexPathForRow:0 inSection:SETTINGS_SECTION_DISCOVERY_INDEX]; - [tableView scrollToRowAtIndexPath:discoveryIndexPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; - } - else if (section == SETTINGS_SECTION_DISCOVERY_INDEX) - { - [self.settingsDiscoveryTableViewSection selectRow:indexPath.row]; - } - else if (section == SETTINGS_SECTION_IDENTITY_SERVER_INDEX) - { - switch (row) - { - case IDENTITY_SERVER_INDEX: - [self showIdentityServerSettingsScreen]; - break; - } - } - else if (section == SETTINGS_SECTION_IGNORED_USERS_INDEX) - { - MXSession* session = [AppDelegate theDelegate].mxSessions[0]; - - NSString *ignoredUserId; - if (indexPath.row < session.ignoredUsers.count) - { - ignoredUserId = session.ignoredUsers[indexPath.row]; - } - - if (ignoredUserId) - { - [currentAlert dismissViewControllerAnimated:NO completion:nil]; - - __weak typeof(self) weakSelf = self; - - currentAlert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:NSLocalizedStringFromTable(@"settings_unignore_user", @"Vector", nil), ignoredUserId] message:nil preferredStyle:UIAlertControllerStyleAlert]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"yes"] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - - MXSession* session = [AppDelegate theDelegate].mxSessions[0]; - - // Remove the member from the ignored user list - [self startActivityIndicator]; - [session unIgnoreUsers:@[ignoredUserId] success:^{ - - [self stopActivityIndicator]; - - } failure:^(NSError *error) { - - [self stopActivityIndicator]; - - NSLog(@"[SettingsViewController] Unignore %@ failed", ignoredUserId); - - NSString *myUserId = session.myUser.userId; - [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; - - }]; - } - - }]]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"no"] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - } - - }]]; - - [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCUnignoreAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; - } - } - else if (section == SETTINGS_SECTION_OTHER_INDEX) - { - if (row == OTHER_COPYRIGHT_INDEX) - { - WebViewViewController *webViewViewController = [[WebViewViewController alloc] initWithURL:NSLocalizedStringFromTable(@"settings_copyright_url", @"Vector", nil)]; - - webViewViewController.title = NSLocalizedStringFromTable(@"settings_copyright", @"Vector", nil); - - [self pushViewController:webViewViewController]; - } - else if (row == OTHER_TERM_CONDITIONS_INDEX) - { - WebViewViewController *webViewViewController = [[WebViewViewController alloc] initWithURL:NSLocalizedStringFromTable(@"settings_term_conditions_url", @"Vector", nil)]; - - webViewViewController.title = NSLocalizedStringFromTable(@"settings_term_conditions", @"Vector", nil); - - [self pushViewController:webViewViewController]; - } - else if (row == OTHER_PRIVACY_INDEX) - { - WebViewViewController *webViewViewController = [[WebViewViewController alloc] initWithURL:NSLocalizedStringFromTable(@"settings_privacy_policy_url", @"Vector", nil)]; - - webViewViewController.title = NSLocalizedStringFromTable(@"settings_privacy_policy", @"Vector", nil); - - [self pushViewController:webViewViewController]; - } - else if (row == OTHER_THIRD_PARTY_INDEX) - { - NSString *htmlFile = [[NSBundle mainBundle] pathForResource:@"third_party_licenses" ofType:@"html" inDirectory:nil]; - - WebViewViewController *webViewViewController = [[WebViewViewController alloc] initWithLocalHTMLFile:htmlFile]; - - webViewViewController.title = NSLocalizedStringFromTable(@"settings_third_party_notices", @"Vector", nil); - - [self pushViewController:webViewViewController]; - } - } - else if (section == SETTINGS_SECTION_USER_SETTINGS_INDEX) - { - if (row == userSettingsProfilePictureIndex) - { - [self onProfileAvatarTap:nil]; - } - else if (row == userSettingsChangePasswordIndex) - { - [self displayPasswordAlert]; - } - else if (row == userSettingsNewEmailIndex) - { - if (!self.newEmailEditingEnabled) - { - // Enable the new email text field - self.newEmailEditingEnabled = YES; - } - else if (newEmailTextField) - { - [self onAddNewEmail:newEmailTextField]; - } - } - else if (row == userSettingsNewPhoneIndex) - { - if (!self.newPhoneEditingEnabled) - { - // Enable the new phone text field - self.newPhoneEditingEnabled = YES; - } - else if (newPhoneNumberCell.mxkTextField) - { - [self onAddNewPhone:newPhoneNumberCell.mxkTextField]; - } - } - } - else if (section == SETTINGS_SECTION_DEVICES_INDEX) - { - if (row > DEVICES_DESCRIPTION_INDEX) - { - NSUInteger deviceIndex = row - 1; - if (deviceIndex < devicesArray.count) - { - [self showDeviceDetails:devicesArray[deviceIndex]]; - } - } - } - else if (section == SETTINGS_SECTION_CONTACTS_INDEX) - { - if (row == localContactsPhoneBookCountryIndex) - { - CountryPickerViewController *countryPicker = [CountryPickerViewController countryPickerViewController]; - countryPicker.view.tag = SETTINGS_SECTION_CONTACTS_INDEX; - countryPicker.delegate = self; - countryPicker.showCountryCallingCode = YES; - [self pushViewController:countryPicker]; - } - } - - [tableView deselectRowAtIndexPath:indexPath animated:YES]; - } -} - -#pragma mark - actions - - -- (void)onSignout:(id)sender -{ - self.signOutButton = (UIButton*)sender; - - MXKeyBackup *keyBackup = self.mainSession.crypto.backup; - - [self.signOutAlertPresenter presentFor:keyBackup.state - areThereKeysToBackup:keyBackup.hasKeysToBackup - from:self - sourceView:self.signOutButton - animated:YES]; -} - -- (void)onRemove3PID:(NSIndexPath*)path -{ - NSUInteger section = path.section; - NSUInteger row = path.row; - - if (section == SETTINGS_SECTION_USER_SETTINGS_INDEX) - { - NSString *address, *medium; - MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; - NSString *promptMsg; - - if (userSettingsEmailStartIndex <= row && row < userSettingsNewEmailIndex) - { - medium = kMX3PIDMediumEmail; - row = row - userSettingsEmailStartIndex; - NSArray *linkedEmails = account.linkedEmails; - if (row < linkedEmails.count) - { - address = linkedEmails[row]; - promptMsg = [NSString stringWithFormat:NSLocalizedStringFromTable(@"settings_remove_email_prompt_msg", @"Vector", nil), address]; - } - } - else if (userSettingsPhoneStartIndex <= row && row < userSettingsNewPhoneIndex) - { - medium = kMX3PIDMediumMSISDN; - row = row - userSettingsPhoneStartIndex; - NSArray *linkedPhones = account.linkedPhoneNumbers; - if (row < linkedPhones.count) - { - address = linkedPhones[row]; - NSString *e164 = [NSString stringWithFormat:@"+%@", address]; - NBPhoneNumber *phoneNb = [[NBPhoneNumberUtil sharedInstance] parse:e164 defaultRegion:nil error:nil]; - NSString *phoneMunber = [[NBPhoneNumberUtil sharedInstance] format:phoneNb numberFormat:NBEPhoneNumberFormatINTERNATIONAL error:nil]; - - promptMsg = [NSString stringWithFormat:NSLocalizedStringFromTable(@"settings_remove_phone_prompt_msg", @"Vector", nil), phoneMunber]; - } - } - - if (address && medium) - { - __weak typeof(self) weakSelf = self; - - if (currentAlert) - { - [currentAlert dismissViewControllerAnimated:NO completion:nil]; - currentAlert = nil; - } - - // Remove ? - currentAlert = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"settings_remove_prompt_title", @"Vector", nil) message:promptMsg preferredStyle:UIAlertControllerStyleAlert]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] - style:UIAlertActionStyleCancel - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - } - - }]]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"remove", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - - [self startActivityIndicator]; - - [self.mainSession.matrixRestClient remove3PID:address medium:medium success:^{ - - if (weakSelf) - { - typeof(self) self = weakSelf; - - [self stopActivityIndicator]; - - // Update linked 3pids - [self loadAccount3PIDs]; - } - - } failure:^(NSError *error) { - - NSLog(@"[SettingsViewController] Remove 3PID: %@ failed", address); - if (weakSelf) - { - typeof(self) self = weakSelf; - - [self stopActivityIndicator]; - - NSString *myUserId = self.mainSession.myUser.userId; // TODO: Hanlde multi-account - [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; - } - }]; - } - - }]]; - - [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCRemove3PIDAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; - } - } -} - -- (void)togglePushNotifications:(id)sender -{ - // Check first whether the user allow notification from device settings - UIUserNotificationType currentUserNotificationTypes = UIApplication.sharedApplication.currentUserNotificationSettings.types; - if (currentUserNotificationTypes == UIUserNotificationTypeNone) - { - [currentAlert dismissViewControllerAnimated:NO completion:nil]; - - __weak typeof(self) weakSelf = self; - - NSString *appDisplayName = [[NSBundle mainBundle] infoDictionary][@"CFBundleDisplayName"]; - - currentAlert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:NSLocalizedStringFromTable(@"settings_on_denied_notification", @"Vector", nil), appDisplayName] message:nil preferredStyle:UIAlertControllerStyleAlert]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - } - - }]]; - - [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCPushNotificationsAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; - - // Keep off the switch - ((UISwitch*)sender).on = NO; - } - else if ([MXKAccountManager sharedManager].activeAccounts.count) - { - [self startActivityIndicator]; - - MXKAccountManager *accountManager = [MXKAccountManager sharedManager]; - MXKAccount* account = accountManager.activeAccounts.firstObject; - - if (accountManager.pushDeviceToken) - { - [account enablePushKitNotifications:!account.isPushKitNotificationActive success:^{ - [self stopActivityIndicator]; - } failure:^(NSError *error) { - [self stopActivityIndicator]; - }]; - } - else - { - // Obtain device token when user has just enabled access to notifications from system settings - [[AppDelegate theDelegate] registerForRemoteNotificationsWithCompletion:^(NSError * error) { - if (error) - { - [(UISwitch *)sender setOn:NO animated:YES]; - [self stopActivityIndicator]; - } - else - { - [account enablePushKitNotifications:YES success:^{ - [self stopActivityIndicator]; - } failure:^(NSError *error) { - [self stopActivityIndicator]; - }]; - } - }]; - } - } -} - -- (void)toggleCallKit:(id)sender -{ - UISwitch *switchButton = (UISwitch*)sender; - [MXKAppSettings standardAppSettings].enableCallKit = switchButton.isOn; -} - -- (void)toggleStunServerFallback:(id)sender -{ - UISwitch *switchButton = (UISwitch*)sender; - RiotSettings.shared.allowStunServerFallback = switchButton.isOn; - - self.mainSession.callManager.fallbackSTUNServer = RiotSettings.shared.allowStunServerFallback ? RiotSettings.shared.stunServerFallback : nil; -} - -- (void)toggleAllowIntegrations:(id)sender -{ - UISwitch *switchButton = (UISwitch*)sender; - - MXSession *session = self.mainSession; - [self startActivityIndicator]; - - __block RiotSharedSettings *sharedSettings = [[RiotSharedSettings alloc] initWithSession:session]; - [sharedSettings setIntegrationProvisioningWithEnabled:switchButton.on success:^{ - sharedSettings = nil; - [self stopActivityIndicator]; - } failure:^(NSError * _Nullable error) { - sharedSettings = nil; - [switchButton setOn:!switchButton.on animated:YES]; - [self stopActivityIndicator]; - }]; -} - -- (void)toggleShowDecodedContent:(id)sender -{ - UISwitch *switchButton = (UISwitch*)sender; - RiotSettings.shared.showDecryptedContentInNotifications = switchButton.isOn; -} - -- (void)toggleLocalContactsSync:(id)sender -{ - UISwitch *switchButton = (UISwitch*)sender; - - if (switchButton.on) - { - [MXKContactManager requestUserConfirmationForLocalContactsSyncInViewController:self completionHandler:^(BOOL granted) { - - [MXKAppSettings standardAppSettings].syncLocalContacts = granted; - - [self.tableView reloadData]; - }]; - } - else - { - [MXKAppSettings standardAppSettings].syncLocalContacts = NO; - - [self.tableView reloadData]; - } -} - -- (void)toggleSendCrashReport:(id)sender -{ - BOOL enable = RiotSettings.shared.enableCrashReport; - if (enable) - { - NSLog(@"[SettingsViewController] disable automatic crash report and analytics sending"); - - RiotSettings.shared.enableCrashReport = NO; - - [[Analytics sharedInstance] stop]; - - // Remove potential crash file. - [MXLogger deleteCrashLog]; - } - else - { - NSLog(@"[SettingsViewController] enable automatic crash report and analytics sending"); - - RiotSettings.shared.enableCrashReport = YES; - - [[Analytics sharedInstance] start]; - } -} - -- (void)toggleEnableRageShake:(id)sender -{ - if (sender && [sender isKindOfClass:UISwitch.class]) - { - UISwitch *switchButton = (UISwitch*)sender; - - RiotSettings.shared.enableRageShake = switchButton.isOn; - - [self.tableView reloadData]; - } -} - -- (void)toggleSyncWithLazyLoadOfRoomMembers:(id)sender -{ - if (sender && [sender isKindOfClass:UISwitch.class]) - { - UISwitch *switchButton = (UISwitch*)sender; - - if (!switchButton.isOn) - { - // Disable LL and reload - [MXKAppSettings standardAppSettings].syncWithLazyLoadOfRoomMembers = NO; - [self launchClearCache]; - } - else - { - switchButton.enabled = NO; - [self startActivityIndicator]; - - // Check the user homeserver supports lazy-loading - MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; - - MXWeakify(self); - [account supportLazyLoadOfRoomMembers:^(BOOL supportLazyLoadOfRoomMembers) { - MXStrongifyAndReturnIfNil(self); - - if (supportLazyLoadOfRoomMembers) - { - // Lazy-loading is fully supported, enable it - [MXKAppSettings standardAppSettings].syncWithLazyLoadOfRoomMembers = YES; - [self launchClearCache]; - } - else - { - [switchButton setOn:NO animated:YES]; - switchButton.enabled = YES; - [self stopActivityIndicator]; - - // No support of lazy-loading, do not engage it and warn the user - [self->currentAlert dismissViewControllerAnimated:NO completion:nil]; - - self->currentAlert = [UIAlertController alertControllerWithTitle:nil - message:NSLocalizedStringFromTable(@"settings_labs_room_members_lazy_loading_error_message", @"Vector", nil) - preferredStyle:UIAlertControllerStyleAlert]; - - MXWeakify(self); - [self->currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - MXStrongifyAndReturnIfNil(self); - self->currentAlert = nil; - }]]; - - [self->currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCNoHSSupportOfLazyLoading"]; - [self presentViewController:self->currentAlert animated:YES completion:nil]; - } - }]; - } - } -} - - -- (void)toggleJitsiForConference:(id)sender -{ - if (sender && [sender isKindOfClass:UISwitch.class]) - { - UISwitch *switchButton = (UISwitch*)sender; - - RiotSettings.shared.createConferenceCallsWithJitsi = switchButton.isOn; - - [self.tableView reloadData]; - } -} - -- (void)toggleLabsCrossSigning:(id)sender -{ - UISwitch *switchButton = (UISwitch*)sender; - - RiotSettings.shared.enableCrossSigning = switchButton.isOn; -} - -- (void)toggleBlacklistUnverifiedDevices:(id)sender -{ - UISwitch *switchButton = (UISwitch*)sender; - - MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; - account.mxSession.crypto.globalBlacklistUnverifiedDevices = switchButton.on; - - [self.tableView reloadData]; -} - -- (void)togglePinRoomsWithMissedNotif:(id)sender -{ - UISwitch *switchButton = (UISwitch*)sender; - - RiotSettings.shared.pinRoomsWithMissedNotificationsOnHome = switchButton.on; -} - -- (void)togglePinRoomsWithUnread:(id)sender -{ - UISwitch *switchButton = (UISwitch*)sender; - - RiotSettings.shared.pinRoomsWithUnreadMessagesOnHome = switchButton.on; -} - -- (void)toggleCommunityFlair:(id)sender -{ - UISwitch *switchButton = (UISwitch*)sender; - - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:switchButton.tag inSection:groupsDataSource.joinedGroupsSection]; - id groupCellData = [groupsDataSource cellDataAtIndex:indexPath]; - MXGroup *group = groupCellData.group; - - if (group) - { - [self startActivityIndicator]; - - __weak typeof(self) weakSelf = self; - - [self.mainSession updateGroupPublicity:group isPublicised:switchButton.on success:^{ - - if (weakSelf) - { - typeof(self) self = weakSelf; - [self stopActivityIndicator]; - } - - } failure:^(NSError *error) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - [self stopActivityIndicator]; - - // Come back to previous state button - [switchButton setOn:!switchButton.isOn animated:YES]; - - // Notify user - [[AppDelegate theDelegate] showErrorAsAlert:error]; - } - }]; - } -} - -- (void)markAllAsRead:(id)sender -{ - // Feedback: disable button and run activity indicator - UIButton *button = (UIButton*)sender; - button.enabled = NO; - [self startActivityIndicator]; - - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - - [[AppDelegate theDelegate] markAllMessagesAsRead]; - - [self stopActivityIndicator]; - button.enabled = YES; - - }); -} - -- (void)clearCache:(id)sender -{ - // Feedback: disable button and run activity indicator - UIButton *button = (UIButton*)sender; - button.enabled = NO; - - [self launchClearCache]; -} - -- (void)launchClearCache -{ - [self startActivityIndicator]; - - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - - [[AppDelegate theDelegate] reloadMatrixSessions:YES]; - - }); -} - -- (void)reportBug:(id)sender -{ - BugReportViewController *bugReportViewController = [BugReportViewController bugReportViewController]; - [bugReportViewController showInViewController:self]; -} - -- (void)selectPhoneNumberCountry:(id)sender -{ - newPhoneNumberCountryPicker = [CountryPickerViewController countryPickerViewController]; - newPhoneNumberCountryPicker.view.tag = SETTINGS_SECTION_USER_SETTINGS_INDEX; - newPhoneNumberCountryPicker.delegate = self; - newPhoneNumberCountryPicker.showCountryCallingCode = YES; - [self pushViewController:newPhoneNumberCountryPicker]; -} - -//- (void)onRuleUpdate:(id)sender -//{ -// MXPushRule* pushRule = nil; -// MXSession* session = [[AppDelegate theDelegate].mxSessions objectAtIndex:0]; -// -// NSInteger row = ((UIView*)sender).tag; -// -// if (row == NOTIFICATION_SETTINGS_CONTAINING_MY_DISPLAY_NAME_INDEX) -// { -// pushRule = [session.notificationCenter ruleById:kMXNotificationCenterContainDisplayNameRuleID]; -// } -// else if (row == NOTIFICATION_SETTINGS_CONTAINING_MY_USER_NAME_INDEX) -// { -// pushRule = [session.notificationCenter ruleById:kMXNotificationCenterContainUserNameRuleID]; -// } -// else if (row == NOTIFICATION_SETTINGS_SENT_TO_ME_INDEX) -// { -// pushRule = [session.notificationCenter ruleById:kMXNotificationCenterOneToOneRoomRuleID]; -// } -// else if (row == NOTIFICATION_SETTINGS_INVITED_TO_ROOM_INDEX) -// { -// pushRule = [session.notificationCenter ruleById:kMXNotificationCenterInviteMeRuleID]; -// } -// else if (row == NOTIFICATION_SETTINGS_PEOPLE_LEAVE_JOIN_INDEX) -// { -// pushRule = [session.notificationCenter ruleById:kMXNotificationCenterMemberEventRuleID]; -// } -// else if (row == NOTIFICATION_SETTINGS_CALL_INVITATION_INDEX) -// { -// pushRule = [session.notificationCenter ruleById:kMXNotificationCenterCallRuleID]; -// } -// -// if (pushRule) -// { -// // toggle the rule -// [session.notificationCenter enableRule:pushRule isEnabled:!pushRule.enabled]; -// } -//} - - -- (void)onSave:(id)sender -{ - // sanity check - if ([MXKAccountManager sharedManager].activeAccounts.count == 0) - { - return; - } - - self.navigationItem.rightBarButtonItem.enabled = NO; - [self startActivityIndicator]; - isSavingInProgress = YES; - __weak typeof(self) weakSelf = self; - - MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; - MXMyUser* myUser = account.mxSession.myUser; - - if (newDisplayName && ![myUser.displayname isEqualToString:newDisplayName]) - { - // Save display name - [account setUserDisplayName:newDisplayName success:^{ - - if (weakSelf) - { - // Update the current displayname - typeof(self) self = weakSelf; - self->newDisplayName = nil; - - // Go to the next change saving step - [self onSave:nil]; - } - - } failure:^(NSError *error) { - - NSLog(@"[SettingsViewController] Failed to set displayName"); - - if (weakSelf) - { - typeof(self) self = weakSelf; - [self handleErrorDuringProfileChangeSaving:error]; - } - - }]; - - return; - } - - if (newAvatarImage) - { - // Retrieve the current picture and make sure its orientation is up - UIImage *updatedPicture = [MXKTools forceImageOrientationUp:newAvatarImage]; - - // Upload picture - MXMediaLoader *uploader = [MXMediaManager prepareUploaderWithMatrixSession:account.mxSession initialRange:0 andRange:1.0]; - - [uploader uploadData:UIImageJPEGRepresentation(updatedPicture, 0.5) filename:nil mimeType:@"image/jpeg" success:^(NSString *url) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - // Store uploaded picture url and trigger picture saving - self->uploadedAvatarURL = url; - self->newAvatarImage = nil; - [self onSave:nil]; - } - - - } failure:^(NSError *error) { - - NSLog(@"[SettingsViewController] Failed to upload image"); - - if (weakSelf) - { - typeof(self) self = weakSelf; - [self handleErrorDuringProfileChangeSaving:error]; - } - - }]; - - return; - } - else if (uploadedAvatarURL) - { - [account setUserAvatarUrl:uploadedAvatarURL - success:^{ - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->uploadedAvatarURL = nil; - [self onSave:nil]; - } - - } - failure:^(NSError *error) { - - NSLog(@"[SettingsViewController] Failed to set avatar url"); - - if (weakSelf) - { - typeof(self) self = weakSelf; - [self handleErrorDuringProfileChangeSaving:error]; - } - - }]; - - return; - } - - // Backup is complete - isSavingInProgress = NO; - [self stopActivityIndicator]; - - // Check whether destroy has been called durign saving - if (onReadyToDestroyHandler) - { - // Ready to destroy - onReadyToDestroyHandler(); - onReadyToDestroyHandler = nil; - } - else - { - [self.tableView reloadData]; - } -} - -- (void)handleErrorDuringProfileChangeSaving:(NSError*)error -{ - // Sanity check: retrieve the current root view controller - UIViewController *rootViewController = [AppDelegate theDelegate].window.rootViewController; - if (rootViewController) - { - __weak typeof(self) weakSelf = self; - - // Alert user - NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey]; - if (!title) - { - title = [NSBundle mxk_localizedStringForKey:@"settings_fail_to_update_profile"]; - } - NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey]; - - [currentAlert dismissViewControllerAnimated:NO completion:nil]; - - currentAlert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - self->currentAlert = nil; - - // Reset the updated displayname - self->newDisplayName = nil; - - // Discard picture change - self->uploadedAvatarURL = nil; - self->newAvatarImage = nil; - - // Loop to end saving - [self onSave:nil]; - } - - }]]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"retry"] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - self->currentAlert = nil; - - // Loop to retry saving - [self onSave:nil]; - } - - }]]; - - [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCSaveChangesFailedAlert"]; - [rootViewController presentViewController:currentAlert animated:YES completion:nil]; - } -} - -- (IBAction)onAddNewEmail:(id)sender -{ - // Ignore empty field - if (!newEmailTextField.text.length) - { - // Reset new email adding - self.newEmailEditingEnabled = NO; - return; - } - - // Email check - if (![MXTools isEmailAddress:newEmailTextField.text]) - { - __weak typeof(self) weakSelf = self; - - [currentAlert dismissViewControllerAnimated:NO completion:nil]; - - currentAlert = [UIAlertController alertControllerWithTitle:[NSBundle mxk_localizedStringForKey:@"account_error_email_wrong_title"] message:[NSBundle mxk_localizedStringForKey:@"account_error_email_wrong_description"] preferredStyle:UIAlertControllerStyleAlert]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - self->currentAlert = nil; - } - - }]]; - - [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCAddEmailAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; - - return; - } - - // Dismiss the keyboard - [newEmailTextField resignFirstResponder]; - - MXSession* session = [AppDelegate theDelegate].mxSessions[0]; - - [self checkAuthenticationFlowForAdding:kMX3PIDMediumEmail withSession:session onComplete:^(NSString *password) { - - [self startActivityIndicator]; - - __block MX3PidAddSession *thirdPidAddSession; - thirdPidAddSession = [session.threePidAddManager startAddEmailSessionWithEmail:self->newEmailTextField.text nextLink:nil success:^{ - - [self showValidationEmailDialogWithMessage:[NSBundle mxk_localizedStringForKey:@"account_email_validation_message"] - for3PidAddSession:thirdPidAddSession - threePidAddManager:session.threePidAddManager - password:password]; - - } failure:^(NSError * _Nonnull error) { - - [self stopActivityIndicator]; - - NSLog(@"[SettingsViewController] Failed to request email token"); - - // Translate the potential MX error. - MXError *mxError = [[MXError alloc] initWithNSError:error]; - if (mxError - && ([mxError.errcode isEqualToString:kMXErrCodeStringThreePIDInUse] - || [mxError.errcode isEqualToString:kMXErrCodeStringServerNotTrusted])) - { - NSMutableDictionary *userInfo; - if (error.userInfo) - { - userInfo = [NSMutableDictionary dictionaryWithDictionary:error.userInfo]; - } - else - { - userInfo = [NSMutableDictionary dictionary]; - } - - userInfo[NSLocalizedFailureReasonErrorKey] = nil; - - if ([mxError.errcode isEqualToString:kMXErrCodeStringThreePIDInUse]) - { - userInfo[NSLocalizedDescriptionKey] = NSLocalizedStringFromTable(@"auth_email_in_use", @"Vector", nil); - userInfo[@"error"] = NSLocalizedStringFromTable(@"auth_email_in_use", @"Vector", nil); - } - else - { - userInfo[NSLocalizedDescriptionKey] = NSLocalizedStringFromTable(@"auth_untrusted_id_server", @"Vector", nil); - userInfo[@"error"] = NSLocalizedStringFromTable(@"auth_untrusted_id_server", @"Vector", nil); - } - - error = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo]; - } - else if ([error.domain isEqualToString:MX3PidAddManagerErrorDomain] - && error.code == MX3PidAddManagerErrorDomainIdentityServerRequired) - { - error = [NSError errorWithDomain:error.domain - code:error.code - userInfo:@{ - NSLocalizedDescriptionKey: [NSBundle mxk_localizedStringForKey:@"auth_email_is_required"] - }]; - } - - // Notify user - NSString *myUserId = session.myUser.userId; // TODO: Hanlde multi-account - [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; - - }]; - }]; -} - -- (IBAction)onAddNewPhone:(id)sender -{ - // Ignore empty field - if (!newPhoneNumberCell.mxkTextField.text.length) - { - // Disable the new phone edition if the text field is empty - self.newPhoneEditingEnabled = NO; - return; - } - - // Phone check - if (![[NBPhoneNumberUtil sharedInstance] isValidNumber:newPhoneNumber]) - { - [currentAlert dismissViewControllerAnimated:NO completion:nil]; - __weak typeof(self) weakSelf = self; - - currentAlert = [UIAlertController alertControllerWithTitle:[NSBundle mxk_localizedStringForKey:@"account_error_msisdn_wrong_title"] message:[NSBundle mxk_localizedStringForKey:@"account_error_msisdn_wrong_description"] preferredStyle:UIAlertControllerStyleAlert]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - } - - }]]; - - [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCAddMsisdnAlert"]; - [self presentViewController:currentAlert animated:YES completion:nil]; - - return; - } - - // Dismiss the keyboard - [newPhoneNumberCell.mxkTextField resignFirstResponder]; - - MXSession* session = [AppDelegate theDelegate].mxSessions[0]; - - NSString *e164 = [[NBPhoneNumberUtil sharedInstance] format:newPhoneNumber numberFormat:NBEPhoneNumberFormatE164 error:nil]; - NSString *msisdn; - if ([e164 hasPrefix:@"+"]) - { - msisdn = e164; - } - else if ([e164 hasPrefix:@"00"]) - { - msisdn = [NSString stringWithFormat:@"+%@", [e164 substringFromIndex:2]]; - } - - [self checkAuthenticationFlowForAdding:kMX3PIDMediumMSISDN withSession:session onComplete:^(NSString *password) { - [self startActivityIndicator]; - - __block MX3PidAddSession *new3Pid; - new3Pid = [session.threePidAddManager startAddPhoneNumberSessionWithPhoneNumber:msisdn countryCode:nil success:^{ - - [self showValidationMsisdnDialogWithMessage:[NSBundle mxk_localizedStringForKey:@"account_msisdn_validation_message"] for3PidAddSession:new3Pid threePidAddManager:session.threePidAddManager password:password]; - - } failure:^(NSError *error) { - - [self stopActivityIndicator]; - - NSLog(@"[SettingsViewController] Failed to request msisdn token"); - - // Translate the potential MX error. - MXError *mxError = [[MXError alloc] initWithNSError:error]; - if (mxError - && ([mxError.errcode isEqualToString:kMXErrCodeStringThreePIDInUse] - || [mxError.errcode isEqualToString:kMXErrCodeStringServerNotTrusted])) - { - NSMutableDictionary *userInfo; - if (error.userInfo) - { - userInfo = [NSMutableDictionary dictionaryWithDictionary:error.userInfo]; - } - else - { - userInfo = [NSMutableDictionary dictionary]; - } - - userInfo[NSLocalizedFailureReasonErrorKey] = nil; - - if ([mxError.errcode isEqualToString:kMXErrCodeStringThreePIDInUse]) - { - userInfo[NSLocalizedDescriptionKey] = NSLocalizedStringFromTable(@"auth_phone_in_use", @"Vector", nil); - userInfo[@"error"] = NSLocalizedStringFromTable(@"auth_phone_in_use", @"Vector", nil); - } - else - { - userInfo[NSLocalizedDescriptionKey] = NSLocalizedStringFromTable(@"auth_untrusted_id_server", @"Vector", nil); - userInfo[@"error"] = NSLocalizedStringFromTable(@"auth_untrusted_id_server", @"Vector", nil); - } - - error = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo]; - } - else if ([error.domain isEqualToString:MX3PidAddManagerErrorDomain] - && error.code == MX3PidAddManagerErrorDomainIdentityServerRequired) - { - error = [NSError errorWithDomain:error.domain - code:error.code - userInfo:@{ - NSLocalizedDescriptionKey: [NSBundle mxk_localizedStringForKey:@"auth_phone_is_required"] - }]; - } - - // Notify user - NSString *myUserId = session.myUser.userId; - [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; - }]; - }]; -} - -- (void)updateSaveButtonStatus -{ - if ([AppDelegate theDelegate].mxSessions.count > 0) - { - MXSession* session = [AppDelegate theDelegate].mxSessions[0]; - MXMyUser* myUser = session.myUser; - - BOOL saveButtonEnabled = (nil != newAvatarImage); - - if (!saveButtonEnabled) - { - if (newDisplayName) - { - saveButtonEnabled = ![myUser.displayname isEqualToString:newDisplayName]; - } - } - - self.navigationItem.rightBarButtonItem.enabled = saveButtonEnabled; - } -} - -- (void)onProfileAvatarTap:(UITapGestureRecognizer *)recognizer -{ - SingleImagePickerPresenter *singleImagePickerPresenter = [[SingleImagePickerPresenter alloc] initWithSession:self.mainSession]; - singleImagePickerPresenter.delegate = self; - - - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:userSettingsProfilePictureIndex inSection:SETTINGS_SECTION_USER_SETTINGS_INDEX]; - UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; - - UIView *sourceView = cell; - - [singleImagePickerPresenter presentFrom:self sourceView:sourceView sourceRect:sourceView.bounds animated:YES]; - - self.imagePickerPresenter = singleImagePickerPresenter; -} - -- (void)exportEncryptionKeys:(UITapGestureRecognizer *)recognizer -{ - [currentAlert dismissViewControllerAnimated:NO completion:nil]; - - exportView = [[MXKEncryptionKeysExportView alloc] initWithMatrixSession:self.mainSession]; - currentAlert = exportView.alertController; - - // Use a temporary file for the export - keyExportsFile = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"riot-keys.txt"]]; - - // Make sure the file is empty - [self deleteKeyExportFile]; - - // Show the export dialog - __weak typeof(self) weakSelf = self; - [exportView showInViewController:self toExportKeysToFile:keyExportsFile onComplete:^(BOOL success) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - self->exportView = nil; - - if (success) - { - // Let another app handling this file - self->documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:keyExportsFile]; - [self->documentInteractionController setDelegate:self]; - - if ([self->documentInteractionController presentOptionsMenuFromRect:self.view.bounds inView:self.view animated:YES]) - { - // We want to delete the temp keys file after it has been processed by the other app. - // We use [UIDocumentInteractionControllerDelegate didEndSendingToApplication] for that - // but it is not reliable for all cases (see http://stackoverflow.com/a/21867096). - // So, arm a timer to auto delete the file after 10mins. - keyExportsFileDeletionTimer = [NSTimer scheduledTimerWithTimeInterval:600 target:self selector:@selector(deleteKeyExportFile) userInfo:self repeats:NO]; - } - else - { - self->documentInteractionController = nil; - [self deleteKeyExportFile]; - } - } - } - }]; -} - -- (void)deleteKeyExportFile -{ - // Cancel the deletion timer if it is still here - if (keyExportsFileDeletionTimer) - { - [keyExportsFileDeletionTimer invalidate]; - keyExportsFileDeletionTimer = nil; - } - - // And delete the file - if (keyExportsFile && [[NSFileManager defaultManager] fileExistsAtPath:keyExportsFile.path]) - { - [[NSFileManager defaultManager] removeItemAtPath:keyExportsFile.path error:nil]; - } -} - -- (void)showThemePicker -{ - __weak typeof(self) weakSelf = self; - - __block UIAlertAction *autoAction, *lightAction, *darkAction, *blackAction; - NSString *themePickerMessage; - - void (^actionBlock)(UIAlertAction *action) = ^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - NSString *newTheme; - if (action == autoAction) - { - newTheme = @"auto"; - } - else if (action == lightAction) - { - newTheme = @"light"; - } - else if (action == darkAction) - { - newTheme = @"dark"; - } - else if (action == blackAction) - { - newTheme = @"black"; - } - - NSString *theme = RiotSettings.shared.userInterfaceTheme; - if (newTheme && ![newTheme isEqualToString:theme]) - { - // Clear fake Riot Avatars based on the previous theme. - [AvatarGenerator clear]; - - // The user wants to select this theme - RiotSettings.shared.userInterfaceTheme = newTheme; - ThemeService.shared.themeId = newTheme; - - [self.tableView reloadData]; - } - } - }; - - if (@available(iOS 11.0, *)) - { - // Show "auto" only from iOS 11 - autoAction = [UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"settings_ui_theme_auto", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:actionBlock]; - - // Explain what is "auto" - themePickerMessage = NSLocalizedStringFromTable(@"settings_ui_theme_picker_message", @"Vector", nil); - } - - lightAction = [UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"settings_ui_theme_light", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:actionBlock]; - - darkAction = [UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"settings_ui_theme_dark", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:actionBlock]; - blackAction = [UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"settings_ui_theme_black", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:actionBlock]; - - - UIAlertController *themePicker = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"settings_ui_theme_picker_title", @"Vector", nil) - message:themePickerMessage - preferredStyle:UIAlertControllerStyleActionSheet]; - - if (autoAction) - { - [themePicker addAction:autoAction]; - } - [themePicker addAction:lightAction]; - [themePicker addAction:darkAction]; - [themePicker addAction:blackAction]; - - // Cancel button - [themePicker addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] - style:UIAlertActionStyleCancel - handler:nil]]; - - UIView *fromCell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:USER_INTERFACE_THEME_INDEX inSection:SETTINGS_SECTION_USER_INTERFACE_INDEX]]; - [themePicker popoverPresentationController].sourceView = fromCell; - [themePicker popoverPresentationController].sourceRect = fromCell.bounds; - - [self presentViewController:themePicker animated:YES completion:nil]; -} - -- (void)deactivateAccountAction -{ - DeactivateAccountViewController *deactivateAccountViewController = [DeactivateAccountViewController instantiateWithMatrixSession:self.mainSession]; - - UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:deactivateAccountViewController]; - navigationController.modalPresentationStyle = UIModalPresentationFormSheet; - - [self presentViewController:navigationController animated:YES completion:nil]; - - deactivateAccountViewController.delegate = self; - - self.deactivateAccountViewController = deactivateAccountViewController; -} - -#pragma mark - TextField listener - -- (IBAction)textFieldDidChange:(id)sender -{ - UITextField* textField = (UITextField*)sender; - - if (textField.tag == userSettingsDisplayNameIndex) - { - // Remove white space from both ends - newDisplayName = [textField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; - [self updateSaveButtonStatus]; - } - else if (textField.tag == userSettingsNewPhoneIndex) - { - newPhoneNumber = [[NBPhoneNumberUtil sharedInstance] parse:textField.text defaultRegion:newPhoneNumberCell.isoCountryCode error:nil]; - - [self formatNewPhoneNumber]; - } -} - -- (IBAction)textFieldDidEnd:(id)sender -{ - UITextField* textField = (UITextField*)sender; - - // Disable the new email edition if the user leaves the text field empty - if (textField.tag == userSettingsNewEmailIndex && textField.text.length == 0 && !keepNewEmailEditing) - { - self.newEmailEditingEnabled = NO; - } - else if (textField.tag == userSettingsNewPhoneIndex && textField.text.length == 0 && !keepNewPhoneNumberEditing && !newPhoneNumberCountryPicker) - { - // Disable the new phone edition if the user leaves the text field empty - self.newPhoneEditingEnabled = NO; - } -} - -#pragma mark - UITextField delegate - -- (void)textFieldDidBeginEditing:(UITextField *)textField -{ - if (textField.tag == userSettingsDisplayNameIndex) - { - textField.textAlignment = NSTextAlignmentLeft; - } -} -- (void)textFieldDidEndEditing:(UITextField *)textField -{ - if (textField.tag == userSettingsDisplayNameIndex) - { - textField.textAlignment = NSTextAlignmentRight; + return NSLocalizedStringFromTable(@"settings_key_backup", @"Vector", nil); + } } + + return nil; } -- (BOOL)textFieldShouldReturn:(UITextField *)textField +- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section { - if (textField.tag == userSettingsDisplayNameIndex) - { - [textField resignFirstResponder]; - } - else if (textField.tag == userSettingsNewEmailIndex) + if ([view isKindOfClass:UITableViewHeaderFooterView.class]) { - [self onAddNewEmail:textField]; + // Customize label style + UITableViewHeaderFooterView *tableViewHeaderFooterView = (UITableViewHeaderFooterView*)view; + tableViewHeaderFooterView.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + tableViewHeaderFooterView.textLabel.font = [UIFont systemFontOfSize:15]; } - - return YES; } -#pragma password update management -- (IBAction)passwordTextFieldDidChange:(id)sender -{ - savePasswordAction.enabled = (currentPasswordTextField.text.length > 0) && (newPasswordTextField1.text.length > 2) && [newPasswordTextField1.text isEqualToString:newPasswordTextField2.text]; -} +#pragma mark - UITableView delegate -- (void)displayPasswordAlert +- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath; { - __weak typeof(self) weakSelf = self; - [resetPwdAlertController dismissViewControllerAnimated:NO completion:nil]; - - resetPwdAlertController = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"settings_change_password", @"Vector", nil) message:nil preferredStyle:UIAlertControllerStyleAlert]; - resetPwdAlertController.accessibilityLabel=@"ChangePasswordAlertController"; - savePasswordAction = [UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"save", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - self->resetPwdAlertController = nil; - - if ([MXKAccountManager sharedManager].activeAccounts.count > 0) - { - [self startActivityIndicator]; - self->isResetPwdInProgress = YES; - - MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; - - [account changePassword:currentPasswordTextField.text with:newPasswordTextField1.text success:^{ - - if (weakSelf) - { - typeof(self) self = weakSelf; - - self->isResetPwdInProgress = NO; - [self stopActivityIndicator]; - - // Display a successful message only if the settings screen is still visible (destroy is not called yet) - if (!self->onReadyToDestroyHandler) - { - [self->currentAlert dismissViewControllerAnimated:NO completion:nil]; - - self->currentAlert = [UIAlertController alertControllerWithTitle:nil message:NSLocalizedStringFromTable(@"settings_password_updated", @"Vector", nil) preferredStyle:UIAlertControllerStyleAlert]; - - [self->currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - - // Check whether destroy has been called durign pwd change - if (self->onReadyToDestroyHandler) - { - // Ready to destroy - self->onReadyToDestroyHandler(); - self->onReadyToDestroyHandler = nil; - } - } - - }]]; - - [self->currentAlert mxk_setAccessibilityIdentifier:@"SettingsVCOnPasswordUpdatedAlert"]; - [self presentViewController:self->currentAlert animated:YES completion:nil]; - } - else - { - // Ready to destroy - self->onReadyToDestroyHandler(); - self->onReadyToDestroyHandler = nil; - } - } - - } failure:^(NSError *error) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - self->isResetPwdInProgress = NO; - [self stopActivityIndicator]; - - // Display a failure message on the current screen - UIViewController *rootViewController = [AppDelegate theDelegate].window.rootViewController; - if (rootViewController) - { - [self->currentAlert dismissViewControllerAnimated:NO completion:nil]; - - self->currentAlert = [UIAlertController alertControllerWithTitle:nil message:NSLocalizedStringFromTable(@"settings_fail_to_update_password", @"Vector", nil) preferredStyle:UIAlertControllerStyleAlert]; - - [self->currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - self->currentAlert = nil; - - // Check whether destroy has been called durign pwd change - if (self->onReadyToDestroyHandler) - { - // Ready to destroy - self->onReadyToDestroyHandler(); - self->onReadyToDestroyHandler = nil; - } - } - - }]]; - - [self->currentAlert mxk_setAccessibilityIdentifier:@"SettingsVCPasswordChangeFailedAlert"]; - [rootViewController presentViewController:self->currentAlert animated:YES completion:nil]; - } - } - - }]; - } - } - - }]; - - // disable by default - // check if the textfields have the right value - savePasswordAction.enabled = NO; - - UIAlertAction* cancel = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - self->resetPwdAlertController = nil; - } - - }]; - - [resetPwdAlertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - - self->currentPasswordTextField = textField; - self->currentPasswordTextField.placeholder = NSLocalizedStringFromTable(@"settings_old_password", @"Vector", nil); - self->currentPasswordTextField.secureTextEntry = YES; - [self->currentPasswordTextField addTarget:self action:@selector(passwordTextFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; - } - - }]; + cell.backgroundColor = ThemeService.shared.theme.backgroundColor; - [resetPwdAlertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { - - if (weakSelf) + if (cell.selectionStyle != UITableViewCellSelectionStyleNone) + { + // Update the selected background view + if (ThemeService.shared.theme.selectedBackgroundColor) { - typeof(self) self = weakSelf; - - self->newPasswordTextField1 = textField; - self->newPasswordTextField1.placeholder = NSLocalizedStringFromTable(@"settings_new_password", @"Vector", nil); - self->newPasswordTextField1.secureTextEntry = YES; - [self->newPasswordTextField1 addTarget:self action:@selector(passwordTextFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; + cell.selectedBackgroundView = [[UIView alloc] init]; + cell.selectedBackgroundView.backgroundColor = ThemeService.shared.theme.selectedBackgroundColor; } - - }]; - - [resetPwdAlertController addTextFieldWithConfigurationHandler:^(UITextField *textField) { - - if (weakSelf) + else { - typeof(self) self = weakSelf; - - self->newPasswordTextField2 = textField; - self->newPasswordTextField2.placeholder = NSLocalizedStringFromTable(@"settings_confirm_password", @"Vector", nil); - self->newPasswordTextField2.secureTextEntry = YES; - [self->newPasswordTextField2 addTarget:self action:@selector(passwordTextFieldDidChange:) forControlEvents:UIControlEventEditingChanged]; + if (tableView.style == UITableViewStylePlain) + { + cell.selectedBackgroundView = nil; + } + else + { + cell.selectedBackgroundView.backgroundColor = nil; + } } - }]; - - - [resetPwdAlertController addAction:cancel]; - [resetPwdAlertController addAction:savePasswordAction]; - [self presentViewController:resetPwdAlertController animated:YES completion:nil]; + } } -#pragma mark - UIDocumentInteractionControllerDelegate - -- (void)documentInteractionController:(UIDocumentInteractionController *)controller didEndSendingToApplication:(NSString *)application +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { - // If iOS wants to call this method, this is the right time to remove the file - [self deleteKeyExportFile]; + return 24; } -- (void)documentInteractionControllerDidDismissOptionsMenu:(UIDocumentInteractionController *)controller +- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { - documentInteractionController = nil; + return 24; } -#pragma mark - MXKCountryPickerViewControllerDelegate - -- (void)countryPickerViewController:(MXKCountryPickerViewController *)countryPickerViewController didSelectCountry:(NSString *)isoCountryCode +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - if (countryPickerViewController.view.tag == SETTINGS_SECTION_CONTACTS_INDEX) - { - [MXKAppSettings standardAppSettings].phonebookCountryCode = isoCountryCode; - } - else if (countryPickerViewController.view.tag == SETTINGS_SECTION_USER_SETTINGS_INDEX) + if (self.tableView == tableView) { - if (newPhoneNumberCell) + NSInteger section = indexPath.section; + NSInteger row = indexPath.row; + + if (section == SETTINGS_SECTION_DEVICES_INDEX) { - newPhoneNumberCell.isoCountryCode = isoCountryCode; - - newPhoneNumber = [[NBPhoneNumberUtil sharedInstance] parse:newPhoneNumberCell.mxkTextField.text defaultRegion:isoCountryCode error:nil]; - [self formatNewPhoneNumber]; + if (row > DEVICES_DESCRIPTION_INDEX) + { + NSUInteger deviceIndex = row - 1; + if (deviceIndex < devicesArray.count) + { + [self showDeviceDetails:devicesArray[deviceIndex]]; + } + } } + + [tableView deselectRowAtIndexPath:indexPath animated:YES]; } - - [countryPickerViewController withdrawViewControllerAnimated:YES completion:nil]; } -#pragma mark - MXKCountryPickerViewControllerDelegate +#pragma mark - actions -- (void)languagePickerViewController:(MXKLanguagePickerViewController *)languagePickerViewController didSelectLangugage:(NSString *)language +- (void)exportEncryptionKeys:(UITapGestureRecognizer *)recognizer { - [languagePickerViewController withdrawViewControllerAnimated:YES completion:nil]; + [currentAlert dismissViewControllerAnimated:NO completion:nil]; - if (![language isEqualToString:[NSBundle mxk_language]] - || (language == nil && [NSBundle mxk_language])) - { - [NSBundle mxk_setLanguage:language]; + exportView = [[MXKEncryptionKeysExportView alloc] initWithMatrixSession:self.mainSession]; + currentAlert = exportView.alertController; - // Store user settings - NSUserDefaults *sharedUserDefaults = [MXKAppSettings standardAppSettings].sharedUserDefaults; - [sharedUserDefaults setObject:language forKey:@"appLanguage"]; + // Use a temporary file for the export + keyExportsFile = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"riot-keys.txt"]]; - // Do a reload in order to recompute strings in the new language - // Note that "reloadMatrixSessions:NO" will reset room summaries - [self startActivityIndicator]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + // Make sure the file is empty + [self deleteKeyExportFile]; - [[AppDelegate theDelegate] reloadMatrixSessions:NO]; - }); + // Show the export dialog + __weak typeof(self) weakSelf = self; + [exportView showInViewController:self toExportKeysToFile:keyExportsFile onComplete:^(BOOL success) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->currentAlert = nil; + self->exportView = nil; + + if (success) + { + // Let another app handling this file + self->documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:keyExportsFile]; + [self->documentInteractionController setDelegate:self]; + + if ([self->documentInteractionController presentOptionsMenuFromRect:self.view.bounds inView:self.view animated:YES]) + { + // We want to delete the temp keys file after it has been processed by the other app. + // We use [UIDocumentInteractionControllerDelegate didEndSendingToApplication] for that + // but it is not reliable for all cases (see http://stackoverflow.com/a/21867096). + // So, arm a timer to auto delete the file after 10mins. + keyExportsFileDeletionTimer = [NSTimer scheduledTimerWithTimeInterval:600 target:self selector:@selector(deleteKeyExportFile) userInfo:self repeats:NO]; + } + else + { + self->documentInteractionController = nil; + [self deleteKeyExportFile]; + } + } + } + }]; +} + +- (void)deleteKeyExportFile +{ + // Cancel the deletion timer if it is still here + if (keyExportsFileDeletionTimer) + { + [keyExportsFileDeletionTimer invalidate]; + keyExportsFileDeletionTimer = nil; + } + + // And delete the file + if (keyExportsFile && [[NSFileManager defaultManager] fileExistsAtPath:keyExportsFile.path]) + { + [[NSFileManager defaultManager] removeItemAtPath:keyExportsFile.path error:nil]; } } + + #pragma mark - MXKDataSourceDelegate - (Class)cellViewClassForCellData:(MXKCellData*)cellData @@ -4525,21 +1210,6 @@ - (void)dataSource:(MXKDataSource *)dataSource didCellChange:(id)changes [self refreshSettings]; } -#pragma mark - DeactivateAccountViewControllerDelegate - -- (void)deactivateAccountViewControllerDidDeactivateWithSuccess:(DeactivateAccountViewController *)deactivateAccountViewController -{ - NSLog(@"[SettingsViewController] Deactivate account with success"); - - [[AppDelegate theDelegate] logoutSendingRequestServer:NO completion:^(BOOL isLoggedOut) { - NSLog(@"[SettingsViewController] Complete clear user data after account deactivation"); - }]; -} - -- (void)deactivateAccountViewControllerDidCancel:(DeactivateAccountViewController *)deactivateAccountViewController -{ - [deactivateAccountViewController dismissViewControllerAnimated:YES completion:nil]; -} #pragma mark - SettingsKeyBackupTableViewSectionDelegate @@ -4675,151 +1345,4 @@ - (void)keyBackupRecoverCoordinatorBridgePresenterDidRecover:(KeyBackupRecoverCo keyBackupRecoverCoordinatorBridgePresenter = nil; } -#pragma mark - SignOutAlertPresenterDelegate - -- (void)signOutAlertPresenterDidTapBackupAction:(SignOutAlertPresenter * _Nonnull)presenter -{ - [self showKeyBackupSetupFromSignOutFlow:YES]; -} - -- (void)signOutAlertPresenterDidTapSignOutAction:(SignOutAlertPresenter * _Nonnull)presenter -{ - // Prevent user to perform user interaction in settings when sign out - // TODO: Prevent user interaction in all application (navigation controller and split view controller included) - self.view.userInteractionEnabled = NO; - self.signOutButton.enabled = NO; - - [self startActivityIndicator]; - - MXWeakify(self); - - [[AppDelegate theDelegate] logoutWithConfirmation:NO completion:^(BOOL isLoggedOut) { - MXStrongifyAndReturnIfNil(self); - - [self stopActivityIndicator]; - - self.view.userInteractionEnabled = YES; - self.signOutButton.enabled = YES; - }]; -} - -#pragma mark - SingleImagePickerPresenterDelegate - -- (void)singleImagePickerPresenterDidCancel:(SingleImagePickerPresenter *)presenter -{ - [presenter dismissWithAnimated:YES completion:nil]; - self.imagePickerPresenter = nil; -} - -- (void)singleImagePickerPresenter:(SingleImagePickerPresenter *)presenter didSelectImageData:(NSData *)imageData withUTI:(MXKUTI *)uti -{ - [presenter dismissWithAnimated:YES completion:nil]; - self.imagePickerPresenter = nil; - - newAvatarImage = [UIImage imageWithData:imageData]; - - [self.tableView reloadData]; -} - - -#pragma mark - Identity Server updates - -- (void)registerAccountDataDidChangeIdentityServerNotification -{ - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAccountDataDidChangeIdentityServerNotification:) name:kMXSessionAccountDataDidChangeIdentityServerNotification object:nil]; -} - -- (void)handleAccountDataDidChangeIdentityServerNotification:(NSNotification*)notification -{ - [self refreshSettings]; -} - -#pragma mark - SettingsDiscoveryTableViewSectionDelegate - -- (void)settingsDiscoveryTableViewSectionDidUpdate:(SettingsDiscoveryTableViewSection *)settingsDiscoveryTableViewSection -{ - [self.tableView reloadData]; -} - -- (MXKTableViewCell *)settingsDiscoveryTableViewSection:(SettingsDiscoveryTableViewSection *)settingsDiscoveryTableViewSection tableViewCellClass:(Class)tableViewCellClass forRow:(NSInteger)forRow -{ - MXKTableViewCell *tableViewCell; - - if ([tableViewCellClass isEqual:[MXKTableViewCell class]]) - { - tableViewCell = [self getDefaultTableViewCell:self.tableView]; - } - else if ([tableViewCellClass isEqual:[MXKTableViewCellWithTextView class]]) - { - tableViewCell = [self textViewCellForTableView:self.tableView atIndexPath:[NSIndexPath indexPathForRow:forRow inSection:SETTINGS_SECTION_DISCOVERY_INDEX]]; - } - else if ([tableViewCellClass isEqual:[MXKTableViewCellWithButton class]]) - { - MXKTableViewCellWithButton *cell = [self.tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; - - if (!cell) - { - cell = [[MXKTableViewCellWithButton alloc] init]; - } - else - { - // Fix https://github.com/vector-im/riot-ios/issues/1354 - cell.mxkButton.titleLabel.text = nil; - } - - cell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; - [cell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; - - tableViewCell = cell; - } - else if ([tableViewCellClass isEqual:[MXKTableViewCellWithLabelAndSwitch class]]) - { - tableViewCell = [self getLabelAndSwitchCell:self.tableView forIndexPath:[NSIndexPath indexPathForRow:forRow inSection:SETTINGS_SECTION_DISCOVERY_INDEX]]; - } - - return tableViewCell; -} - -#pragma mark - SettingsDiscoveryViewModelCoordinatorDelegate - -- (void)settingsDiscoveryViewModel:(SettingsDiscoveryViewModel *)viewModel didSelectThreePidWith:(NSString *)medium and:(NSString *)address -{ - SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter *discoveryThreePidDetailsPresenter = [[SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter alloc] initWithSession:self.mainSession medium:medium adress:address]; - - MXWeakify(self); - - [discoveryThreePidDetailsPresenter pushFrom:self.navigationController animated:YES popCompletion:^{ - MXStrongifyAndReturnIfNil(self); - - self.discoveryThreePidDetailsPresenter = nil; - }]; - - self.discoveryThreePidDetailsPresenter = discoveryThreePidDetailsPresenter; -} - -- (void)settingsDiscoveryViewModelDidTapUserSettingsLink:(SettingsDiscoveryViewModel *)viewModel -{ - NSIndexPath *discoveryIndexPath = [NSIndexPath indexPathForRow:userSettingsNewEmailIndex inSection:SETTINGS_SECTION_USER_SETTINGS_INDEX]; - [self.tableView scrollToRowAtIndexPath:discoveryIndexPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; -} - - -#pragma mark - Identity Server - -- (void)showIdentityServerSettingsScreen -{ - identityServerSettingsCoordinatorBridgePresenter = [[SettingsIdentityServerCoordinatorBridgePresenter alloc] initWithSession:self.mainSession]; - - [identityServerSettingsCoordinatorBridgePresenter pushFrom:self.navigationController animated:YES popCompletion:nil]; - identityServerSettingsCoordinatorBridgePresenter.delegate = self; -} - -#pragma mark - SettingsIdentityServerCoordinatorBridgePresenterDelegate - -- (void)settingsIdentityServerCoordinatorBridgePresenterDelegateDidComplete:(SettingsIdentityServerCoordinatorBridgePresenter *)coordinatorBridgePresenter -{ - identityServerSettingsCoordinatorBridgePresenter = nil; - [self refreshSettings]; -} - @end From 9f9f74ec24d11ee67ec04850f062d94e2ff3d73b Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 28 Jan 2020 22:36:50 +0100 Subject: [PATCH 112/282] Settings > Security: More cleaning --- .../Security/SecurityViewController.h | 3 +- .../Security/SecurtiyViewController.m | 491 ++++-------------- 2 files changed, 104 insertions(+), 390 deletions(-) diff --git a/Riot/Modules/Settings/Security/SecurityViewController.h b/Riot/Modules/Settings/Security/SecurityViewController.h index de3aefaa27..0ff4e9d971 100644 --- a/Riot/Modules/Settings/Security/SecurityViewController.h +++ b/Riot/Modules/Settings/Security/SecurityViewController.h @@ -18,9 +18,8 @@ #import "DeviceView.h" -#import "MediaPickerViewController.h" -@interface SecurityViewController : MXKTableViewController +@interface SecurityViewController : MXKTableViewController + (SecurityViewController*)instantiateWithMatrixSession:(MXSession*)matrixSession; diff --git a/Riot/Modules/Settings/Security/SecurtiyViewController.m b/Riot/Modules/Settings/Security/SecurtiyViewController.m index 2eb9a26783..ea34b5e3d4 100644 --- a/Riot/Modules/Settings/Security/SecurtiyViewController.m +++ b/Riot/Modules/Settings/Security/SecurtiyViewController.m @@ -23,27 +23,10 @@ #import "AppDelegate.h" #import "AvatarGenerator.h" -#import "BugReportViewController.h" - -#import "WebViewViewController.h" - -#import "CountryPickerViewController.h" -#import "LanguagePickerViewController.h" -#import "DeactivateAccountViewController.h" - -#import "NBPhoneNumberUtil.h" -#import "RageShakeManager.h" #import "ThemeService.h" -#import "TableViewCellWithPhoneNumberTextField.h" - -#import "GroupsDataSource.h" -#import "GroupTableViewCellWithSwitch.h" - -#import "GBDeviceInfo_iOS.h" #import "Riot-Swift.h" -NSString* const kSettingsViewControllerPhoneBookCountryCellId2 = @"kSettingsViewControllerPhoneBookCountryCellId2"; enum { @@ -65,95 +48,28 @@ DEVICES_DESCRIPTION_INDEX = 0 }; -#define SECTION_TITLE_PADDING_WHEN_HIDDEN 0.01f - -typedef void (^blockSettingsViewController_onReadyToDestroy)(void); - -@interface SecurityViewController () +UIDocumentInteractionControllerDelegate> { // Current alert (if any). UIAlertController *currentAlert; - // listener - id removedAccountObserver; - id accountUserInfoObserver; - id pushInfoUpdateObserver; - - id notificationCenterWillUpdateObserver; - id notificationCenterDidUpdateObserver; - id notificationCenterDidFailObserver; - - // profile updates - // avatar - UIImage* newAvatarImage; - // the avatar image has been uploaded - NSString* uploadedAvatarURL; - - // new display name - NSString* newDisplayName; - - // password update - UITextField* currentPasswordTextField; - UITextField* newPasswordTextField1; - UITextField* newPasswordTextField2; - UIAlertAction* savePasswordAction; - - // New email address to bind - UITextField* newEmailTextField; - - // New phone number to bind - TableViewCellWithPhoneNumberTextField * newPhoneNumberCell; - CountryPickerViewController *newPhoneNumberCountryPicker; - NBPhoneNumber *newPhoneNumber; - - // Dynamic rows in the user settings section - NSInteger userSettingsProfilePictureIndex; - NSInteger userSettingsDisplayNameIndex; - NSInteger userSettingsFirstNameIndex; - NSInteger userSettingsSurnameIndex; - NSInteger userSettingsEmailStartIndex; // The user can have several linked emails. Hence, the dynamic section items count - NSInteger userSettingsNewEmailIndex; // This index also marks the end of the emails list - NSInteger userSettingsPhoneStartIndex; // The user can have several linked phone numbers. Hence, the dynamic section items count - NSInteger userSettingsNewPhoneIndex; // This index also marks the end of the phone numbers list - NSInteger userSettingsChangePasswordIndex; - NSInteger userSettingsThreePidsInformation; - NSInteger userSettingsNightModeSepIndex; - NSInteger userSettingsNightModeIndex; - - // Dynamic rows in the local contacts section - NSInteger localContactsSyncIndex; - NSInteger localContactsPhoneBookCountryIndex; - // Devices NSMutableArray *devicesArray; DeviceView *deviceView; - // Flair: the groups data source - GroupsDataSource *groupsDataSource; - // Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar. id kAppDelegateDidTapStatusBarNotificationObserver; // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. id kThemeServiceDidChangeThemeNotificationObserver; - - // Postpone destroy operation when saving, pwd reset or email binding is in progress - BOOL isSavingInProgress; - BOOL isResetPwdInProgress; - BOOL is3PIDBindingInProgress; - blockSettingsViewController_onReadyToDestroy onReadyToDestroyHandler; - - // - UIAlertController *resetPwdAlertController; // The view used to export e2e keys MXKEncryptionKeysExportView *exportView; @@ -163,38 +79,14 @@ @interface SecurityViewController () notificationObserver = self.themeDidChangeNotificationObserver; -//// -//// if (notificationObserver) -//// { -//// [[NSNotificationCenter defaultCenter] removeObserver:notificationObserver]; -//// } -// -// [super destroy]; -//} - #pragma mark - View life cycle @@ -230,10 +110,6 @@ - (void)finalizeInit // Setup `MXKViewControllerHandling` properties self.enableBarTintColorStatusChange = NO; self.rageShakeManager = [RageShakeManager sharedManager]; - - isSavingInProgress = NO; - isResetPwdInProgress = NO; - is3PIDBindingInProgress = NO; } - (void)viewDidLoad @@ -245,12 +121,8 @@ - (void)viewDidLoad // Remove back bar button title when pushing a view controller self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; - - [self.tableView registerClass:MXKTableViewCellWithLabelAndTextField.class forCellReuseIdentifier:[MXKTableViewCellWithLabelAndTextField defaultReuseIdentifier]]; + [self.tableView registerClass:MXKTableViewCellWithLabelAndSwitch.class forCellReuseIdentifier:[MXKTableViewCellWithLabelAndSwitch defaultReuseIdentifier]]; - [self.tableView registerClass:MXKTableViewCellWithLabelAndMXKImageView.class forCellReuseIdentifier:[MXKTableViewCellWithLabelAndMXKImageView defaultReuseIdentifier]]; - [self.tableView registerClass:TableViewCellWithPhoneNumberTextField.class forCellReuseIdentifier:[TableViewCellWithPhoneNumberTextField defaultReuseIdentifier]]; - [self.tableView registerClass:GroupTableViewCellWithSwitch.class forCellReuseIdentifier:[GroupTableViewCellWithSwitch defaultReuseIdentifier]]; [self.tableView registerNib:MXKTableViewCellWithTextView.nib forCellReuseIdentifier:[MXKTableViewCellWithTextView defaultReuseIdentifier]]; // Enable self sizing cells @@ -308,13 +180,6 @@ - (void)didReceiveMemoryWarning - (void)destroy { - if (groupsDataSource) - { - groupsDataSource.delegate = nil; - [groupsDataSource destroy]; - groupsDataSource = nil; - } - // Release the potential pushed view controller [self releasePushedViewController]; @@ -331,30 +196,8 @@ - (void)destroy kThemeServiceDidChangeThemeNotificationObserver = nil; } - if (isSavingInProgress || isResetPwdInProgress || is3PIDBindingInProgress) - { - __weak typeof(self) weakSelf = self; - onReadyToDestroyHandler = ^() { - - if (weakSelf) - { - typeof(self) self = weakSelf; - [self destroy]; - } - - }; - } - else - { - // Dispose all resources - [self reset]; - - [super destroy]; - } - keyBackupSetupCoordinatorBridgePresenter = nil; keyBackupRecoverCoordinatorBridgePresenter = nil; - identityServerSettingsCoordinatorBridgePresenter = nil; } - (void)onMatrixSessionStateDidChange:(NSNotification *)notif @@ -379,34 +222,25 @@ - (void)viewWillAppear:(BOOL)animated // Screen tracking [[Analytics sharedInstance] trackScreen:@"Settings"]; - + // Release the potential pushed view controller [self releasePushedViewController]; - + // Refresh display [self refreshSettings]; // Refresh the current device information in parallel [self loadCurrentDeviceInformation]; - + // Refresh devices in parallel [self loadDevices]; - + // Observe kAppDelegateDidTapStatusBarNotificationObserver. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - + [self.tableView setContentOffset:CGPointMake(-self.tableView.mxk_adjustedContentInset.left, -self.tableView.mxk_adjustedContentInset.top) animated:YES]; - - }]; - - newPhoneNumberCountryPicker = nil; -} -- (void)viewDidAppear:(BOOL)animated -{ - [super viewDidAppear:animated]; - - [self.settingsDiscoveryTableViewSection reload]; + }]; } - (void)viewWillDisappear:(BOOL)animated @@ -418,31 +252,7 @@ - (void)viewWillDisappear:(BOOL)animated [currentAlert dismissViewControllerAnimated:NO completion:nil]; currentAlert = nil; } - - if (resetPwdAlertController) - { - [resetPwdAlertController dismissViewControllerAnimated:NO completion:nil]; - resetPwdAlertController = nil; - } - if (notificationCenterWillUpdateObserver) - { - [[NSNotificationCenter defaultCenter] removeObserver:notificationCenterWillUpdateObserver]; - notificationCenterWillUpdateObserver = nil; - } - - if (notificationCenterDidUpdateObserver) - { - [[NSNotificationCenter defaultCenter] removeObserver:notificationCenterDidUpdateObserver]; - notificationCenterDidUpdateObserver = nil; - } - - if (notificationCenterDidFailObserver) - { - [[NSNotificationCenter defaultCenter] removeObserver:notificationCenterDidFailObserver]; - notificationCenterDidFailObserver = nil; - } - if (kAppDelegateDidTapStatusBarNotificationObserver) { [[NSNotificationCenter defaultCenter] removeObserver:kAppDelegateDidTapStatusBarNotificationObserver]; @@ -456,10 +266,10 @@ - (void)pushViewController:(UIViewController*)viewController { // Keep ref on pushed view controller pushedViewController = viewController; - + // Hide back button title self.navigationItem.backBarButtonItem =[[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; - + [self.navigationController pushViewController:viewController animated:YES]; } @@ -482,45 +292,16 @@ - (void)releasePushedViewController { [(id)pushedViewController destroy]; } - + pushedViewController = nil; } } -- (void)dismissKeyboard -{ - [currentPasswordTextField resignFirstResponder]; - [newPasswordTextField1 resignFirstResponder]; - [newPasswordTextField2 resignFirstResponder]; - [newEmailTextField resignFirstResponder]; - [newPhoneNumberCell.mxkTextField resignFirstResponder]; -} - - (void)reset { // Remove observers - if (removedAccountObserver) - { - [[NSNotificationCenter defaultCenter] removeObserver:removedAccountObserver]; - removedAccountObserver = nil; - } - - if (accountUserInfoObserver) - { - [[NSNotificationCenter defaultCenter] removeObserver:accountUserInfoObserver]; - accountUserInfoObserver = nil; - } - - if (pushInfoUpdateObserver) - { - [[NSNotificationCenter defaultCenter] removeObserver:pushInfoUpdateObserver]; - pushInfoUpdateObserver = nil; - } - [[NSNotificationCenter defaultCenter] removeObserver:self]; - - onReadyToDestroyHandler = nil; - + if (deviceView) { [deviceView removeFromSuperview]; @@ -533,11 +314,11 @@ - (void)loadCurrentDeviceInformation // Refresh the current device information MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; [account loadDeviceInformation:^{ - + // Refresh all the table (A slide down animation is observed when we limit the refresh to the concerned section). // Note: The use of 'reloadData' handles the case where the account has been logged out. [self refreshSettings]; - + } failure:nil]; } @@ -545,7 +326,7 @@ - (NSAttributedString*)cryptographyInformation { // TODO Handle multi accounts MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; - + // Crypto information NSMutableAttributedString *cryptoInformationString = [[NSMutableAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"settings_crypto_device_name", @"Vector", nil) @@ -555,7 +336,7 @@ - (NSAttributedString*)cryptographyInformation initWithString:account.device.displayName ? account.device.displayName : @"" attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor, NSFontAttributeName: [UIFont systemFontOfSize:17]}]]; - + [cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"settings_crypto_device_id", @"Vector", nil) attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor, @@ -564,7 +345,7 @@ - (NSAttributedString*)cryptographyInformation initWithString:account.device.deviceId ? account.device.deviceId : @"" attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor, NSFontAttributeName: [UIFont systemFontOfSize:17]}]]; - + [cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"settings_crypto_device_key", @"Vector", nil) attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor, @@ -578,7 +359,7 @@ - (NSAttributedString*)cryptographyInformation initWithString:fingerprint ? fingerprint : @"" attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor, NSFontAttributeName: [UIFont boldSystemFontOfSize:17]}]]; - + return cryptoInformationString; } @@ -586,15 +367,18 @@ - (void)loadDevices { // Refresh the account devices list MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; + + MXWeakify(self); [account.mxRestClient devices:^(NSArray *devices) { - + MXStrongifyAndReturnIfNil(self); + if (devices) { - devicesArray = [NSMutableArray arrayWithArray:devices]; - + self->devicesArray = [NSMutableArray arrayWithArray:devices]; + // Sort devices according to the last seen date. NSComparator comparator = ^NSComparisonResult(MXDevice *deviceA, MXDevice *deviceB) { - + if (deviceA.lastSeenTs > deviceB.lastSeenTs) { return NSOrderedAscending; @@ -603,43 +387,41 @@ - (void)loadDevices { return NSOrderedDescending; } - + return NSOrderedSame; }; - + // Sort devices list - [devicesArray sortUsingComparator:comparator]; + [self->devicesArray sortUsingComparator:comparator]; } else { - devicesArray = nil; + self->devicesArray = nil; } - + // Refresh all the table (A slide down animation is observed when we limit the refresh to the concerned section). // Note: The use of 'reloadData' handles the case where the account has been logged out. [self refreshSettings]; - + } failure:^(NSError *error) { - + // Display the data that has been loaded last time // Note: The use of 'reloadData' handles the case where the account has been logged out. [self refreshSettings]; - + }]; } - (void)showDeviceDetails:(MXDevice *)device { - [self dismissKeyboard]; - deviceView = [[DeviceView alloc] initWithDevice:device andMatrixSession:self.mainSession]; deviceView.delegate = self; // Add the view and define edge constraints [self.tableView.superview addSubview:deviceView]; [self.tableView.superview bringSubviewToFront:deviceView]; - + NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:deviceView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual @@ -647,7 +429,7 @@ - (void)showDeviceDetails:(MXDevice *)device attribute:NSLayoutAttributeTop multiplier:1.0f constant:0.0f]; - + NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:deviceView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual @@ -655,7 +437,7 @@ - (void)showDeviceDetails:(MXDevice *)device attribute:NSLayoutAttributeLeft multiplier:1.0f constant:0.0f]; - + NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:deviceView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual @@ -663,7 +445,7 @@ - (void)showDeviceDetails:(MXDevice *)device attribute:NSLayoutAttributeWidth multiplier:1.0f constant:0.0f]; - + NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:deviceView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual @@ -671,14 +453,12 @@ - (void)showDeviceDetails:(MXDevice *)device attribute:NSLayoutAttributeHeight multiplier:1.0f constant:0.0f]; - + [NSLayoutConstraint activateConstraints:@[topConstraint, leftConstraint, widthConstraint, heightConstraint]]; } - (void)deviceView:(DeviceView*)theDeviceView presentAlertController:(UIAlertController *)alert { - [self dismissKeyboard]; - [self presentViewController:alert animated:YES completion:nil]; } @@ -686,59 +466,17 @@ - (void)dismissDeviceView:(MXKDeviceView *)theDeviceView didUpdate:(BOOL)isUpdat { [deviceView removeFromSuperview]; deviceView = nil; - + if (isUpdated) { [self loadDevices]; } } -- (void)editNewEmailTextField -{ - if (newEmailTextField && ![newEmailTextField becomeFirstResponder]) - { - // Retry asynchronously - dispatch_async(dispatch_get_main_queue(), ^{ - - [self editNewEmailTextField]; - - }); - } -} - -- (void)editNewPhoneNumberTextField -{ - if (newPhoneNumberCell && ![newPhoneNumberCell.mxkTextField becomeFirstResponder]) - { - // Retry asynchronously - dispatch_async(dispatch_get_main_queue(), ^{ - - [self editNewPhoneNumberTextField]; - - }); - } -} - - (void)refreshSettings { - // Check whether a text input is currently edited - keepNewEmailEditing = newEmailTextField ? newEmailTextField.isFirstResponder : NO; - keepNewPhoneNumberEditing = newPhoneNumberCell ? newPhoneNumberCell.mxkTextField.isFirstResponder : NO; - // Trigger a full table reloadData [self.tableView reloadData]; - - // Restore the previous edited field - if (keepNewEmailEditing) - { - [self editNewEmailTextField]; - keepNewEmailEditing = NO; - } - else if (keepNewPhoneNumberEditing) - { - [self editNewPhoneNumberTextField]; - keepNewPhoneNumberEditing = NO; - } } - (void)requestAccountPasswordWithTitle:(NSString*)title message:(NSString*)message onComplete:(void (^)(NSString *password))onComplete @@ -785,7 +523,7 @@ - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // Keep ref on destinationViewController [super prepareForSegue:segue sender:sender]; - + // FIXME add night mode } @@ -799,7 +537,7 @@ - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { NSInteger count = 0; - + if (section == SETTINGS_SECTION_DEVICES_INDEX) { count = devicesArray.count; @@ -829,48 +567,20 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger return count; } -- (MXKTableViewCellWithLabelAndTextField*)getLabelAndTextFieldCell:(UITableView*)tableview forIndexPath:(NSIndexPath *)indexPath -{ - MXKTableViewCellWithLabelAndTextField *cell = [tableview dequeueReusableCellWithIdentifier:[MXKTableViewCellWithLabelAndTextField defaultReuseIdentifier] forIndexPath:indexPath]; - - cell.mxkLabelLeadingConstraint.constant = cell.separatorInset.left; - cell.mxkTextFieldLeadingConstraint.constant = 16; - cell.mxkTextFieldTrailingConstraint.constant = 15; - - cell.mxkLabel.textColor = ThemeService.shared.theme.textPrimaryColor; - - cell.mxkTextField.userInteractionEnabled = YES; - cell.mxkTextField.borderStyle = UITextBorderStyleNone; - cell.mxkTextField.textAlignment = NSTextAlignmentRight; - cell.mxkTextField.textColor = ThemeService.shared.theme.textSecondaryColor; - cell.mxkTextField.font = [UIFont systemFontOfSize:16]; - cell.mxkTextField.placeholder = nil; - - cell.accessoryType = UITableViewCellAccessoryNone; - cell.accessoryView = nil; - - cell.alpha = 1.0f; - cell.userInteractionEnabled = YES; - - [cell layoutIfNeeded]; - - return cell; -} - - (MXKTableViewCellWithLabelAndSwitch*)getLabelAndSwitchCell:(UITableView*)tableview forIndexPath:(NSIndexPath *)indexPath { MXKTableViewCellWithLabelAndSwitch *cell = [tableview dequeueReusableCellWithIdentifier:[MXKTableViewCellWithLabelAndSwitch defaultReuseIdentifier] forIndexPath:indexPath]; - + cell.mxkLabelLeadingConstraint.constant = cell.separatorInset.left; cell.mxkSwitchTrailingConstraint.constant = 15; - + cell.mxkLabel.textColor = ThemeService.shared.theme.textPrimaryColor; - + [cell.mxkSwitch removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; - + // Force layout before reusing a cell (fix switch displayed outside the screen) [cell layoutIfNeeded]; - + return cell; } @@ -884,7 +594,7 @@ - (MXKTableViewCell*)getDefaultTableViewCell:(UITableView*)tableView else { cell.selectionStyle = UITableViewCellSelectionStyleDefault; - + cell.accessoryType = UITableViewCellAccessoryNone; cell.accessoryView = nil; } @@ -892,21 +602,21 @@ - (MXKTableViewCell*)getDefaultTableViewCell:(UITableView*)tableView cell.textLabel.font = [UIFont systemFontOfSize:17]; cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; cell.contentView.backgroundColor = UIColor.clearColor; - + return cell; } - (MXKTableViewCellWithTextView*)textViewCellForTableView:(UITableView*)tableView atIndexPath:(NSIndexPath *)indexPath { MXKTableViewCellWithTextView *textViewCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithTextView defaultReuseIdentifier] forIndexPath:indexPath]; - + textViewCell.mxkTextView.textColor = ThemeService.shared.theme.textPrimaryColor; textViewCell.mxkTextView.font = [UIFont systemFontOfSize:17]; textViewCell.mxkTextView.backgroundColor = [UIColor clearColor]; textViewCell.mxkTextViewLeadingConstraint.constant = tableView.separatorInset.left; textViewCell.mxkTextViewTrailingConstraint.constant = tableView.separatorInset.right; textViewCell.mxkTextView.accessibilityIdentifier = nil; - + return textViewCell; } @@ -918,17 +628,15 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N // set the cell to a default value to avoid application crashes UITableViewCell *cell = [[UITableViewCell alloc] init]; cell.backgroundColor = [UIColor redColor]; - + // check if there is a valid session if (([AppDelegate theDelegate].mxSessions.count == 0) || ([MXKAccountManager sharedManager].activeAccounts.count == 0)) { // else use a default cell return cell; } - - MXSession* session = [AppDelegate theDelegate].mxSessions[0]; - MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; + MXSession* session = self.mainSession; if (section == SETTINGS_SECTION_DEVICES_INDEX) { if (row == DEVICES_DESCRIPTION_INDEX) @@ -970,7 +678,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N if (row == CRYPTOGRAPHY_INFO_INDEX) { MXKTableViewCellWithTextView *cryptoCell = [self textViewCellForTableView:tableView atIndexPath:indexPath]; - + cryptoCell.mxkTextView.attributedText = [self cryptographyInformation]; cell = cryptoCell; @@ -980,7 +688,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_crypto_blacklist_unverified_devices", @"Vector", nil); - labelAndSwitchCell.mxkSwitch.on = account.mxSession.crypto.globalBlacklistUnverifiedDevices; + labelAndSwitchCell.mxkSwitch.on = session.crypto.globalBlacklistUnverifiedDevices; labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; labelAndSwitchCell.mxkSwitch.enabled = YES; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleBlacklistUnverifiedDevices:) forControlEvents:UIControlEventTouchUpInside]; @@ -1047,7 +755,7 @@ - (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSectio return NSLocalizedStringFromTable(@"settings_key_backup", @"Vector", nil); } } - + return nil; } @@ -1068,9 +776,9 @@ - (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath; { cell.backgroundColor = ThemeService.shared.theme.backgroundColor; - + if (cell.selectionStyle != UITableViewCellSelectionStyleNone) - { + { // Update the selected background view if (ThemeService.shared.theme.selectedBackgroundColor) { @@ -1119,11 +827,24 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath } } } - + [tableView deselectRowAtIndexPath:indexPath animated:YES]; } } +#pragma mark - UIDocumentInteractionControllerDelegate + +- (void)documentInteractionController:(UIDocumentInteractionController *)controller didEndSendingToApplication:(NSString *)application +{ + // If iOS wants to call this method, this is the right time to remove the file + [self deleteKeyExportFile]; +} + +- (void)documentInteractionControllerDidDismissOptionsMenu:(UIDocumentInteractionController *)controller +{ + documentInteractionController = nil; +} + #pragma mark - actions - (void)exportEncryptionKeys:(UITapGestureRecognizer *)recognizer @@ -1140,34 +861,31 @@ - (void)exportEncryptionKeys:(UITapGestureRecognizer *)recognizer [self deleteKeyExportFile]; // Show the export dialog - __weak typeof(self) weakSelf = self; + MXWeakify(self); [exportView showInViewController:self toExportKeysToFile:keyExportsFile onComplete:^(BOOL success) { + MXStrongifyAndReturnIfNil(self); + + self->currentAlert = nil; + self->exportView = nil; - if (weakSelf) + if (success) { - typeof(self) self = weakSelf; - self->currentAlert = nil; - self->exportView = nil; + // Let another app handling this file + self->documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:self->keyExportsFile]; + [self->documentInteractionController setDelegate:self]; - if (success) + if ([self->documentInteractionController presentOptionsMenuFromRect:self.view.bounds inView:self.view animated:YES]) { - // Let another app handling this file - self->documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:keyExportsFile]; - [self->documentInteractionController setDelegate:self]; - - if ([self->documentInteractionController presentOptionsMenuFromRect:self.view.bounds inView:self.view animated:YES]) - { - // We want to delete the temp keys file after it has been processed by the other app. - // We use [UIDocumentInteractionControllerDelegate didEndSendingToApplication] for that - // but it is not reliable for all cases (see http://stackoverflow.com/a/21867096). - // So, arm a timer to auto delete the file after 10mins. - keyExportsFileDeletionTimer = [NSTimer scheduledTimerWithTimeInterval:600 target:self selector:@selector(deleteKeyExportFile) userInfo:self repeats:NO]; - } - else - { - self->documentInteractionController = nil; - [self deleteKeyExportFile]; - } + // We want to delete the temp keys file after it has been processed by the other app. + // We use [UIDocumentInteractionControllerDelegate didEndSendingToApplication] for that + // but it is not reliable for all cases (see http://stackoverflow.com/a/21867096). + // So, arm a timer to auto delete the file after 10mins. + self->keyExportsFileDeletionTimer = [NSTimer scheduledTimerWithTimeInterval:600 target:self selector:@selector(deleteKeyExportFile) userInfo:self repeats:NO]; + } + else + { + self->documentInteractionController = nil; + [self deleteKeyExportFile]; } } }]; @@ -1189,20 +907,17 @@ - (void)deleteKeyExportFile } } +- (void)toggleBlacklistUnverifiedDevices:(id)sender +{ + UISwitch *switchButton = (UISwitch*)sender; + self.mainSession.crypto.globalBlacklistUnverifiedDevices = switchButton.on; -#pragma mark - MXKDataSourceDelegate - -- (Class)cellViewClassForCellData:(MXKCellData*)cellData -{ - // Return the class used to display a group with a toogle button - return GroupTableViewCellWithSwitch.class; + [self.tableView reloadData]; } -- (NSString *)cellReuseIdentifierForCellData:(MXKCellData*)cellData -{ - return GroupTableViewCellWithSwitch.defaultReuseIdentifier; -} + +#pragma mark - MXKDataSourceDelegate - (void)dataSource:(MXKDataSource *)dataSource didCellChange:(id)changes { @@ -1305,11 +1020,11 @@ - (void)settingsKeyBackup:(SettingsKeyBackupTableViewSection *)settingsKeyBackup - (void)showKeyBackupSetupFromSignOutFlow:(BOOL)showFromSignOutFlow { keyBackupSetupCoordinatorBridgePresenter = [[KeyBackupSetupCoordinatorBridgePresenter alloc] initWithSession:self.mainSession]; - + [keyBackupSetupCoordinatorBridgePresenter presentFrom:self isStartedFromSignOut:showFromSignOutFlow animated:true]; - + keyBackupSetupCoordinatorBridgePresenter.delegate = self; } From 7a2d3b4e07960ce57753fe719ec06eb6fc8df8d6 Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 28 Jan 2020 23:27:45 +0100 Subject: [PATCH 113/282] Settings > Security: More cleaning --- .../Settings/Security/SecurtiyViewController.m | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/Riot/Modules/Settings/Security/SecurtiyViewController.m b/Riot/Modules/Settings/Security/SecurtiyViewController.m index ea34b5e3d4..da40082ec7 100644 --- a/Riot/Modules/Settings/Security/SecurtiyViewController.m +++ b/Riot/Modules/Settings/Security/SecurtiyViewController.m @@ -366,10 +366,8 @@ - (NSAttributedString*)cryptographyInformation - (void)loadDevices { // Refresh the account devices list - MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; - MXWeakify(self); - [account.mxRestClient devices:^(NSArray *devices) { + [self.mainSession.matrixRestClient devices:^(NSArray *devices) { MXStrongifyAndReturnIfNil(self); if (devices) @@ -629,13 +627,6 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N UITableViewCell *cell = [[UITableViewCell alloc] init]; cell.backgroundColor = [UIColor redColor]; - // check if there is a valid session - if (([AppDelegate theDelegate].mxSessions.count == 0) || ([MXKAccountManager sharedManager].activeAccounts.count == 0)) - { - // else use a default cell - return cell; - } - MXSession* session = self.mainSession; if (section == SETTINGS_SECTION_DEVICES_INDEX) { From 6f3ae21797b6f810d36448dba68fb4e36d80928e Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 29 Jan 2020 08:19:48 +0100 Subject: [PATCH 114/282] Settings > Security: Sort out things to match the design --- Riot/Assets/en.lproj/Vector.strings | 15 ++ Riot/Generated/Strings.swift | 36 +++ .../Security/SecurtiyViewController.m | 245 +++++++++--------- .../Modules/Settings/SettingsViewController.m | 4 +- 4 files changed, 169 insertions(+), 131 deletions(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 863d245812..c82e8a04c2 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -416,6 +416,8 @@ "settings_three_pids_management_information_part2" = "Discovery"; "settings_three_pids_management_information_part3" = "."; +"settings_security" = "SECURITY"; + "settings_enable_push_notif" = "Notifications on this device"; "settings_show_decrypted_content" = "Show decrypted content"; "settings_global_settings_info" = "Global notification settings are available on your %@ web client"; @@ -542,6 +544,19 @@ "settings_identity_server_no_is_description" = "You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one above."; +// Security settings +"security_settings_title" = "Security"; +"security_settings_sessions" = "MY SESSIONS"; +"security_settings_sessions_description" = "Trust sessions to grant access to end-to-end encrypted messages. If you don’t recognise a session, change your login password and reset your Message Password used for Message Backup."; + +"security_settings_backup" = "MESSAGE BACKUP"; + +"security_settings_advanced" = "ADVANCED"; +"security_settings_blacklist_unverified_devices" = "Never send messages to untrusted sessions"; +"security_settings_blacklist_unverified_devices_description" = "Verify all of a users sessions to mark them as trusted and send messages to them."; +"security_settings_export_keys_manually" = "Export keys manually"; + + // Identity server settings "identity_server_settings_title" = "Identity Server"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 119a293e25..2a7d5a0bd4 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -2606,6 +2606,38 @@ internal enum VectorL10n { internal static var searchRooms: String { return VectorL10n.tr("Vector", "search_rooms") } + /// ADVANCED + internal static var securitySettingsAdvanced: String { + return VectorL10n.tr("Vector", "security_settings_advanced") + } + /// MESSAGE BACKUP + internal static var securitySettingsBackup: String { + return VectorL10n.tr("Vector", "security_settings_backup") + } + /// Never send messages to untrusted sessions + internal static var securitySettingsBlacklistUnverifiedDevices: String { + return VectorL10n.tr("Vector", "security_settings_blacklist_unverified_devices") + } + /// Verify all of a users sessions to mark them as trusted and send messages to them. + internal static var securitySettingsBlacklistUnverifiedDevicesDescription: String { + return VectorL10n.tr("Vector", "security_settings_blacklist_unverified_devices_description") + } + /// Export keys manually + internal static var securitySettingsExportKeysManually: String { + return VectorL10n.tr("Vector", "security_settings_export_keys_manually") + } + /// MY SESSIONS + internal static var securitySettingsSessions: String { + return VectorL10n.tr("Vector", "security_settings_sessions") + } + /// Trust sessions to grant access to end-to-end encrypted messages. If you don’t recognise a session, change your login password and reset your Message Password used for Message Backup. + internal static var securitySettingsSessionsDescription: String { + return VectorL10n.tr("Vector", "security_settings_sessions_description") + } + /// Security + internal static var securitySettingsTitle: String { + return VectorL10n.tr("Vector", "security_settings_title") + } /// Send to %@ internal static func sendTo(_ p1: String) -> String { return VectorL10n.tr("Vector", "send_to", p1) @@ -3122,6 +3154,10 @@ internal enum VectorL10n { internal static var settingsReportBug: String { return VectorL10n.tr("Vector", "settings_report_bug") } + /// SECURITY + internal static var settingsSecurity: String { + return VectorL10n.tr("Vector", "settings_security") + } /// Send anon crash & usage data internal static var settingsSendCrashReport: String { return VectorL10n.tr("Vector", "settings_send_crash_report") diff --git a/Riot/Modules/Settings/Security/SecurtiyViewController.m b/Riot/Modules/Settings/Security/SecurtiyViewController.m index da40082ec7..27bf4a4f8b 100644 --- a/Riot/Modules/Settings/Security/SecurtiyViewController.m +++ b/Riot/Modules/Settings/Security/SecurtiyViewController.m @@ -30,22 +30,18 @@ enum { - SETTINGS_SECTION_DEVICES_INDEX, - SETTINGS_SECTION_CRYPTOGRAPHY_INDEX, - SETTINGS_SECTION_KEYBACKUP_INDEX, - SETTINGS_SECTION_COUNT + SECTION_SESSIONS, + SECTION_KEYBACKUP, + SECTION_ADVANCED, + SECTION_DEBUG, // TODO: To remove + SECTION_COUNT }; enum { - CRYPTOGRAPHY_INFO_INDEX = 0, - CRYPTOGRAPHY_BLACKLIST_UNVERIFIED_DEVICES_INDEX, - CRYPTOGRAPHY_EXPORT_INDEX, - CRYPTOGRAPHY_COUNT -}; - -enum -{ - DEVICES_DESCRIPTION_INDEX = 0 + ADVANCED_BLACKLIST_UNVERIFIED_DEVICES, + ADVANCED_BLACKLIST_UNVERIFIED_DEVICES_DESCRIPTION, + ADVANCED_EXPORT, // TODO: To move to SECTION_KEYBACKUP + ADVANCED_COUNT }; @@ -117,7 +113,7 @@ - (void)viewDidLoad [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. - self.navigationItem.title = NSLocalizedStringFromTable(@"security_title", @"Vector", nil); + self.navigationItem.title = NSLocalizedStringFromTable(@"security_settings_title", @"Vector", nil); // Remove back bar button title when pushing a view controller self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; @@ -529,37 +525,27 @@ - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - return SETTINGS_SECTION_COUNT; + return SECTION_COUNT; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { NSInteger count = 0; - if (section == SETTINGS_SECTION_DEVICES_INDEX) - { - count = devicesArray.count; - if (count) - { - // For some description (DEVICES_DESCRIPTION_INDEX) - count++; - } - } - else if (section == SETTINGS_SECTION_CRYPTOGRAPHY_INDEX) + switch (section) { - // Check whether this section is visible. - if (self.mainSession.crypto) - { - count = CRYPTOGRAPHY_COUNT; - } - } - else if (section == SETTINGS_SECTION_KEYBACKUP_INDEX) - { - // Check whether this section is visible. - if (self.mainSession.crypto) - { + case SECTION_SESSIONS: + count = devicesArray.count + 1; + break; + case SECTION_KEYBACKUP: count = keyBackupSection.numberOfRows; - } + break; + case SECTION_ADVANCED: + count = ADVANCED_COUNT; + break; + case SECTION_DEBUG: + count = 1; + break; } return count; @@ -604,6 +590,19 @@ - (MXKTableViewCell*)getDefaultTableViewCell:(UITableView*)tableView return cell; } +- (MXKTableViewCell*)descriptionCellForTableView:(UITableView*)tableView withText:(NSString*)text +{ + MXKTableViewCell *cell = [self getDefaultTableViewCell:tableView]; + cell.textLabel.text = text; + cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + cell.textLabel.numberOfLines = 0; + cell.contentView.backgroundColor = ThemeService.shared.theme.headerBackgroundColor; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + + return cell; +} + + - (MXKTableViewCellWithTextView*)textViewCellForTableView:(UITableView*)tableView atIndexPath:(NSIndexPath *)indexPath { MXKTableViewCellWithTextView *textViewCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithTextView defaultReuseIdentifier] forIndexPath:indexPath]; @@ -628,123 +627,110 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N cell.backgroundColor = [UIColor redColor]; MXSession* session = self.mainSession; - if (section == SETTINGS_SECTION_DEVICES_INDEX) + if (section == SECTION_SESSIONS) { - if (row == DEVICES_DESCRIPTION_INDEX) - { - MXKTableViewCell *descriptionCell = [self getDefaultTableViewCell:tableView]; - descriptionCell.textLabel.text = NSLocalizedStringFromTable(@"settings_devices_description", @"Vector", nil); - descriptionCell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; - descriptionCell.textLabel.font = [UIFont systemFontOfSize:15]; - descriptionCell.textLabel.numberOfLines = 0; - descriptionCell.contentView.backgroundColor = ThemeService.shared.theme.headerBackgroundColor; - descriptionCell.selectionStyle = UITableViewCellSelectionStyleNone; - - cell = descriptionCell; - } - else + if (row < devicesArray.count) { - NSUInteger deviceIndex = row - 1; + NSUInteger deviceIndex = row; MXKTableViewCell *deviceCell = [self getDefaultTableViewCell:tableView]; - if (deviceIndex < devicesArray.count) - { - NSString *name = devicesArray[deviceIndex].displayName; - NSString *deviceId = devicesArray[deviceIndex].deviceId; - deviceCell.textLabel.text = (name.length ? [NSString stringWithFormat:@"%@ (%@)", name, deviceId] : [NSString stringWithFormat:@"(%@)", deviceId]); - deviceCell.textLabel.numberOfLines = 0; + NSString *name = devicesArray[deviceIndex].displayName; + NSString *deviceId = devicesArray[deviceIndex].deviceId; + deviceCell.textLabel.text = (name.length ? [NSString stringWithFormat:@"%@ (%@)", name, deviceId] : [NSString stringWithFormat:@"(%@)", deviceId]); + deviceCell.textLabel.numberOfLines = 0; - if ([deviceId isEqualToString:self.mainSession.matrixRestClient.credentials.deviceId]) - { - deviceCell.textLabel.font = [UIFont boldSystemFontOfSize:17]; - } + if ([deviceId isEqualToString:self.mainSession.matrixRestClient.credentials.deviceId]) + { + deviceCell.textLabel.font = [UIFont boldSystemFontOfSize:17]; } cell = deviceCell; } - - } - else if (section == SETTINGS_SECTION_CRYPTOGRAPHY_INDEX) - { - if (row == CRYPTOGRAPHY_INFO_INDEX) + else if (row == devicesArray.count) { - MXKTableViewCellWithTextView *cryptoCell = [self textViewCellForTableView:tableView atIndexPath:indexPath]; + cell = [self descriptionCellForTableView:tableView + withText:NSLocalizedStringFromTable(@"security_settings_sessions_description", @"Vector", nil) ]; - cryptoCell.mxkTextView.attributedText = [self cryptographyInformation]; - - cell = cryptoCell; } - else if (row == CRYPTOGRAPHY_BLACKLIST_UNVERIFIED_DEVICES_INDEX) + } + else if (section == SECTION_ADVANCED) + { + switch (row) { - MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + case ADVANCED_BLACKLIST_UNVERIFIED_DEVICES: + { + MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_crypto_blacklist_unverified_devices", @"Vector", nil); - labelAndSwitchCell.mxkSwitch.on = session.crypto.globalBlacklistUnverifiedDevices; - labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; - labelAndSwitchCell.mxkSwitch.enabled = YES; - [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleBlacklistUnverifiedDevices:) forControlEvents:UIControlEventTouchUpInside]; + labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"security_settings_blacklist_unverified_devices", @"Vector", nil); + labelAndSwitchCell.mxkSwitch.on = session.crypto.globalBlacklistUnverifiedDevices; + labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + labelAndSwitchCell.mxkSwitch.enabled = YES; + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleBlacklistUnverifiedDevices:) forControlEvents:UIControlEventTouchUpInside]; - cell = labelAndSwitchCell; - } - else if (row == CRYPTOGRAPHY_EXPORT_INDEX) - { - MXKTableViewCellWithButton *exportKeysBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; - if (!exportKeysBtnCell) - { - exportKeysBtnCell = [[MXKTableViewCellWithButton alloc] init]; + cell = labelAndSwitchCell; + break; } - else + case ADVANCED_BLACKLIST_UNVERIFIED_DEVICES_DESCRIPTION: { - // Fix https://github.com/vector-im/riot-ios/issues/1354 - exportKeysBtnCell.mxkButton.titleLabel.text = nil; + cell = [self descriptionCellForTableView:tableView + withText:NSLocalizedStringFromTable(@"security_settings_blacklist_unverified_devices_description", @"Vector", nil) ]; + + break; } + case ADVANCED_EXPORT: + { + MXKTableViewCellWithButton *exportKeysBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; + if (!exportKeysBtnCell) + { + exportKeysBtnCell = [[MXKTableViewCellWithButton alloc] init]; + } + else + { + // Fix https://github.com/vector-im/riot-ios/issues/1354 + exportKeysBtnCell.mxkButton.titleLabel.text = nil; + } - NSString *btnTitle = NSLocalizedStringFromTable(@"settings_crypto_export", @"Vector", nil); - [exportKeysBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; - [exportKeysBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; - [exportKeysBtnCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; - exportKeysBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; + NSString *btnTitle = NSLocalizedStringFromTable(@"security_settings_export_keys_manually", @"Vector", nil); + [exportKeysBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; + [exportKeysBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; + [exportKeysBtnCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; + exportKeysBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; - [exportKeysBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; - [exportKeysBtnCell.mxkButton addTarget:self action:@selector(exportEncryptionKeys:) forControlEvents:UIControlEventTouchUpInside]; - exportKeysBtnCell.mxkButton.accessibilityIdentifier = nil; + [exportKeysBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; + [exportKeysBtnCell.mxkButton addTarget:self action:@selector(exportEncryptionKeys:) forControlEvents:UIControlEventTouchUpInside]; + exportKeysBtnCell.mxkButton.accessibilityIdentifier = nil; - cell = exportKeysBtnCell; + cell = exportKeysBtnCell; + break; + } } } - else if (section == SETTINGS_SECTION_KEYBACKUP_INDEX) + else if (section == SECTION_KEYBACKUP) { cell = [keyBackupSection cellForRowAtRow:row]; } + else if (section == SECTION_DEBUG) + { + MXKTableViewCellWithTextView *cryptoCell = [self textViewCellForTableView:tableView atIndexPath:indexPath]; + cryptoCell.mxkTextView.attributedText = [self cryptographyInformation]; + cell = cryptoCell; + } return cell; } - (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { - if (section == SETTINGS_SECTION_DEVICES_INDEX) - { - // Check whether this section is visible - if (devicesArray.count > 0) - { - return NSLocalizedStringFromTable(@"settings_devices", @"Vector", nil); - } - } - else if (section == SETTINGS_SECTION_CRYPTOGRAPHY_INDEX) - { - // Check whether this section is visible - if (self.mainSession.crypto) - { - return NSLocalizedStringFromTable(@"settings_cryptography", @"Vector", nil); - } - } - else if (section == SETTINGS_SECTION_KEYBACKUP_INDEX) + switch (section) { - // Check whether this section is visible - if (self.mainSession.crypto) - { - return NSLocalizedStringFromTable(@"settings_key_backup", @"Vector", nil); - } + case SECTION_SESSIONS: + return NSLocalizedStringFromTable(@"security_settings_sessions", @"Vector", nil); + case SECTION_KEYBACKUP: + return NSLocalizedStringFromTable(@"security_settings_backup", @"Vector", nil); + case SECTION_ADVANCED: + return NSLocalizedStringFromTable(@"security_settings_advanced", @"Vector", nil); + case SECTION_DEBUG: + return @"DEBUG"; } return nil; @@ -792,6 +778,10 @@ - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)ce - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { + if (section == SECTION_SESSIONS) + { + return 44; + } return 24; } @@ -807,15 +797,12 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath NSInteger section = indexPath.section; NSInteger row = indexPath.row; - if (section == SETTINGS_SECTION_DEVICES_INDEX) + if (section == SECTION_SESSIONS) { - if (row > DEVICES_DESCRIPTION_INDEX) + NSUInteger deviceIndex = row; + if (deviceIndex < devicesArray.count) { - NSUInteger deviceIndex = row - 1; - if (deviceIndex < devicesArray.count) - { - [self showDeviceDetails:devicesArray[deviceIndex]]; - } + [self showDeviceDetails:devicesArray[deviceIndex]]; } } @@ -926,7 +913,7 @@ - (void)settingsKeyBackupTableViewSectionDidUpdate:(SettingsKeyBackupTableViewSe - (MXKTableViewCellWithTextView *)settingsKeyBackupTableViewSection:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection textCellForRow:(NSInteger)textCellForRow { - return [self textViewCellForTableView:self.tableView atIndexPath:[NSIndexPath indexPathForRow:textCellForRow inSection:SETTINGS_SECTION_KEYBACKUP_INDEX]]; + return [self textViewCellForTableView:self.tableView atIndexPath:[NSIndexPath indexPathForRow:textCellForRow inSection:SECTION_KEYBACKUP]]; } - (MXKTableViewCellWithButton *)settingsKeyBackupTableViewSection:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection buttonCellForRow:(NSInteger)buttonCellForRow diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index 0bbd570caf..dc32aeb6a9 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -2574,7 +2574,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N { case SECURITY_BUTTON_INDEX: cell = [self getDefaultTableViewCell:tableView]; - cell.textLabel.text = NSLocalizedStringFromTable(@"Security", @"Vector", nil); + cell.textLabel.text = NSLocalizedStringFromTable(@"security_settings_title", @"Vector", nil); cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; break; } @@ -2701,7 +2701,7 @@ - (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSectio } else if (section == SETTINGS_SECTION_SECURITY_INDEX) { - return NSLocalizedStringFromTable(@"SECURITY", @"Vector", nil); + return NSLocalizedStringFromTable(@"settings_security", @"Vector", nil); } else if (section == SETTINGS_SECTION_DEACTIVATE_ACCOUNT_INDEX) { From 89a975481f149cf651dd96a7ae47ff3d8c4f532a Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 29 Jan 2020 09:32:50 +0100 Subject: [PATCH 115/282] Settings > Security: Display shields for devices --- .../Security/SecurtiyViewController.m | 49 +++++++++++++------ 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/Riot/Modules/Settings/Security/SecurtiyViewController.m b/Riot/Modules/Settings/Security/SecurtiyViewController.m index 27bf4a4f8b..5f8c6b8de5 100644 --- a/Riot/Modules/Settings/Security/SecurtiyViewController.m +++ b/Riot/Modules/Settings/Security/SecurtiyViewController.m @@ -578,9 +578,9 @@ - (MXKTableViewCell*)getDefaultTableViewCell:(UITableView*)tableView else { cell.selectionStyle = UITableViewCellSelectionStyleDefault; - cell.accessoryType = UITableViewCellAccessoryNone; cell.accessoryView = nil; + cell.imageView.image = nil; } cell.textLabel.accessibilityIdentifier = nil; cell.textLabel.font = [UIFont systemFontOfSize:17]; @@ -590,6 +590,38 @@ - (MXKTableViewCell*)getDefaultTableViewCell:(UITableView*)tableView return cell; } +- (MXKTableViewCell*)deviceCellWithDevice:(MXDevice*)device forTableView:(UITableView*)tableView +{ + MXKTableViewCell *cell = [self getDefaultTableViewCell:tableView]; + NSString *name = device.displayName; + NSString *deviceId = device.deviceId; + cell.textLabel.text = (name.length ? [NSString stringWithFormat:@"%@ (%@)", name, deviceId] : [NSString stringWithFormat:@"(%@)", deviceId]); + cell.textLabel.numberOfLines = 0; + cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + + if ([deviceId isEqualToString:self.mainSession.matrixRestClient.credentials.deviceId]) + { + cell.textLabel.font = [UIFont boldSystemFontOfSize:17]; + } + + cell.imageView.image = [self shieldImageForDevice:deviceId]; + + return cell; +} + +- (UIImage*)shieldImageForDevice:(NSString*)deviceId +{ + UIImage* shieldImageForDevice = [UIImage imageNamed:@"encryption_warning"]; + MXDeviceInfo *device = [self.mainSession.crypto deviceWithDeviceId:deviceId ofUser:self.mainSession.myUser.userId]; + if (device.trustLevel.isVerified) + { + shieldImageForDevice = [UIImage imageNamed:@"encryption_trusted"]; + } + + return shieldImageForDevice; +} + + - (MXKTableViewCell*)descriptionCellForTableView:(UITableView*)tableView withText:(NSString*)text { MXKTableViewCell *cell = [self getDefaultTableViewCell:tableView]; @@ -631,20 +663,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N { if (row < devicesArray.count) { - NSUInteger deviceIndex = row; - - MXKTableViewCell *deviceCell = [self getDefaultTableViewCell:tableView]; - NSString *name = devicesArray[deviceIndex].displayName; - NSString *deviceId = devicesArray[deviceIndex].deviceId; - deviceCell.textLabel.text = (name.length ? [NSString stringWithFormat:@"%@ (%@)", name, deviceId] : [NSString stringWithFormat:@"(%@)", deviceId]); - deviceCell.textLabel.numberOfLines = 0; - - if ([deviceId isEqualToString:self.mainSession.matrixRestClient.credentials.deviceId]) - { - deviceCell.textLabel.font = [UIFont boldSystemFontOfSize:17]; - } - - cell = deviceCell; + cell = [self deviceCellWithDevice:devicesArray[row] forTableView:tableView]; } else if (row == devicesArray.count) { From d41fca982d2489792a6f22e01c6d990775492af8 Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 29 Jan 2020 15:56:06 +0100 Subject: [PATCH 116/282] Settings > Security: Debug: Add cross-signing information --- .../Security/SecurtiyViewController.m | 92 ++++++++++++++++++- 1 file changed, 87 insertions(+), 5 deletions(-) diff --git a/Riot/Modules/Settings/Security/SecurtiyViewController.m b/Riot/Modules/Settings/Security/SecurtiyViewController.m index 5f8c6b8de5..83e2f55069 100644 --- a/Riot/Modules/Settings/Security/SecurtiyViewController.m +++ b/Riot/Modules/Settings/Security/SecurtiyViewController.m @@ -44,6 +44,13 @@ ADVANCED_COUNT }; +enum { + DEBUG_CRYPTO_INFO, + DEBUG_CROSSSIGNING_INFO, + DEBUG_CROSSSIGNING_BOOTSTRAP, + DEBUG_COUNT +}; + @interface SecurityViewController () < MXKDataSourceDelegate, @@ -359,6 +366,42 @@ - (NSAttributedString*)cryptographyInformation return cryptoInformationString; } +- (NSAttributedString*)crossSigningStatus +{ + MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; + MXCrossSigning *crossSigning = account.mxSession.crypto.crossSigning; + MXCrossSigningInfo *myUserCrossSigningKeys = crossSigning.myUserCrossSigningKeys; + + // Crypto information + NSMutableAttributedString *cryptoInformationString = [NSMutableAttributedString new]; + + [cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc] + initWithString:@"Cross-Signing\n" + attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor, + NSFontAttributeName: [UIFont boldSystemFontOfSize:17]}]]; + + + NSString *crossSigningEnabled = [NSString stringWithFormat:@"Cross-signing is %@.\n", + crossSigning.isBootstrapped ? @"enabled" : + myUserCrossSigningKeys ? @"enabled in read-only" : @"disabled"]; + + [cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc] + initWithString:crossSigningEnabled + attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor, + NSFontAttributeName: [UIFont systemFontOfSize:17]}]]; + + + NSString *crossSigningKeysTrust = [NSString stringWithFormat:@"Keys are %@.\n", + myUserCrossSigningKeys.trustLevel.isVerified ? @"trusted" : @"not trusted"]; + + [cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc] + initWithString:crossSigningKeysTrust + attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor, + NSFontAttributeName: [UIFont systemFontOfSize:17]}]]; + + return cryptoInformationString; +} + - (void)loadDevices { // Refresh the account devices list @@ -544,7 +587,7 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger count = ADVANCED_COUNT; break; case SECTION_DEBUG: - count = 1; + count = DEBUG_COUNT; break; } @@ -705,8 +748,8 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N } else { - // Fix https://github.com/vector-im/riot-ios/issues/1354 exportKeysBtnCell.mxkButton.titleLabel.text = nil; + exportKeysBtnCell.mxkButton.enabled = YES; } NSString *btnTitle = NSLocalizedStringFromTable(@"security_settings_export_keys_manually", @"Vector", nil); @@ -730,9 +773,47 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N } else if (section == SECTION_DEBUG) { - MXKTableViewCellWithTextView *cryptoCell = [self textViewCellForTableView:tableView atIndexPath:indexPath]; - cryptoCell.mxkTextView.attributedText = [self cryptographyInformation]; - cell = cryptoCell; + switch (row) + { + case DEBUG_CRYPTO_INFO: + { + MXKTableViewCellWithTextView *cryptoCell = [self textViewCellForTableView:tableView atIndexPath:indexPath]; + cryptoCell.mxkTextView.attributedText = [self cryptographyInformation]; + cell = cryptoCell; + break; + } + case DEBUG_CROSSSIGNING_INFO: + { + MXKTableViewCellWithTextView *cryptoCell = [self textViewCellForTableView:tableView atIndexPath:indexPath]; + cryptoCell.mxkTextView.attributedText = [self crossSigningStatus]; + cell = cryptoCell; + break; + } + case DEBUG_CROSSSIGNING_BOOTSTRAP: + { + MXKTableViewCellWithButton *exportKeysBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; + if (!exportKeysBtnCell) + { + exportKeysBtnCell = [[MXKTableViewCellWithButton alloc] init]; + } + + NSString *btnTitle = @"Bootstrap cross-signing"; + [exportKeysBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; + [exportKeysBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; + [exportKeysBtnCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; + exportKeysBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; + + [exportKeysBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; + //[exportKeysBtnCell.mxkButton addTarget:self action:@selector(bootstrapCrossSigning:) forControlEvents:UIControlEventTouchUpInside]; + exportKeysBtnCell.mxkButton.accessibilityIdentifier = nil; + + MXCrossSigning *crossSigning = self.mainSession.crypto.crossSigning; + exportKeysBtnCell.mxkButton.enabled = NO; //!crossSigning.myUserCrossSigningKeys; + + cell = exportKeysBtnCell; + break; + } + } } return cell; @@ -947,6 +1028,7 @@ - (MXKTableViewCellWithButton *)settingsKeyBackupTableViewSection:(SettingsKeyBa { // Fix https://github.com/vector-im/riot-ios/issues/1354 cell.mxkButton.titleLabel.text = nil; + cell.mxkButton.enabled = YES; } cell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; From f136c714c6bac4ad8c6eb7a119bd6c25398af827 Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 29 Jan 2020 17:56:24 +0100 Subject: [PATCH 117/282] Settings > Security: Add Manage Session screen --- Riot.xcodeproj/project.pbxproj | 18 + Riot/Assets/en.lproj/Vector.strings | 9 + Riot/Generated/Strings.swift | 24 + .../ManageSession/ManageSession.storyboard | 41 ++ .../ManageSessionViewController.h | 27 + .../ManageSessionViewController.m | 691 ++++++++++++++++++ .../Security/SecurtiyViewController.m | 6 +- 7 files changed, 815 insertions(+), 1 deletion(-) create mode 100644 Riot/Modules/Settings/Security/ManageSession/ManageSession.storyboard create mode 100644 Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.h create mode 100644 Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index f000c7d32f..d002fba7c4 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -100,6 +100,8 @@ 32BF995321FA2A1300698084 /* SettingsKeyBackupViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BF995221FA2A1300698084 /* SettingsKeyBackupViewState.swift */; }; 32BF995521FA2AB700698084 /* SettingsKeyBackupViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BF995421FA2AB700698084 /* SettingsKeyBackupViewAction.swift */; }; 32BF995721FB07A400698084 /* SettingsKeyBackupTableViewSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BF995621FB07A400698084 /* SettingsKeyBackupTableViewSection.swift */; }; + 32D5D16023E1EE2700E3E37C /* ManageSessionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 32D5D15D23E1EE2700E3E37C /* ManageSessionViewController.m */; }; + 32D5D16123E1EE2700E3E37C /* ManageSession.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 32D5D15E23E1EE2700E3E37C /* ManageSession.storyboard */; }; 32DB557522FDADE50016329E /* ServiceTermsModalCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DB556922FDADE50016329E /* ServiceTermsModalCoordinatorType.swift */; }; 32DB557622FDADE50016329E /* ServiceTermsModalCoordinatorBridgePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DB556A22FDADE50016329E /* ServiceTermsModalCoordinatorBridgePresenter.swift */; }; 32DB557722FDADE50016329E /* ServiceTermsModalCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DB556B22FDADE50016329E /* ServiceTermsModalCoordinator.swift */; }; @@ -777,6 +779,9 @@ 32BF995221FA2A1300698084 /* SettingsKeyBackupViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsKeyBackupViewState.swift; sourceTree = ""; }; 32BF995421FA2AB700698084 /* SettingsKeyBackupViewAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsKeyBackupViewAction.swift; sourceTree = ""; }; 32BF995621FB07A400698084 /* SettingsKeyBackupTableViewSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsKeyBackupTableViewSection.swift; sourceTree = ""; }; + 32D5D15D23E1EE2700E3E37C /* ManageSessionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ManageSessionViewController.m; sourceTree = ""; }; + 32D5D15E23E1EE2700E3E37C /* ManageSession.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = ManageSession.storyboard; sourceTree = ""; }; + 32D5D15F23E1EE2700E3E37C /* ManageSessionViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ManageSessionViewController.h; sourceTree = ""; }; 32D7159E2146CC6F00DF59C9 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Vector.strings; sourceTree = ""; }; 32D7159F2146CC7F00DF59C9 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; 32D715A02146CC8800DF59C9 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -1811,6 +1816,7 @@ 3291DC8823E0BE380009732F /* Security */ = { isa = PBXGroup; children = ( + 32D5D15C23E1EE2700E3E37C /* ManageSession */, 3291DC8B23E0BFF10009732F /* SecurityViewController.h */, 3291DC8C23E0BFF10009732F /* SecurtiyViewController.m */, 3291DC8923E0BE820009732F /* Security.storyboard */, @@ -1875,6 +1881,16 @@ path = KeyBackup; sourceTree = ""; }; + 32D5D15C23E1EE2700E3E37C /* ManageSession */ = { + isa = PBXGroup; + children = ( + 32D5D15D23E1EE2700E3E37C /* ManageSessionViewController.m */, + 32D5D15E23E1EE2700E3E37C /* ManageSession.storyboard */, + 32D5D15F23E1EE2700E3E37C /* ManageSessionViewController.h */, + ); + path = ManageSession; + sourceTree = ""; + }; 32DB556722FDADE50016329E /* ServiceTerms */ = { isa = PBXGroup; children = ( @@ -4215,6 +4231,7 @@ B1B557C120EF5B4500210D55 /* DisabledRoomInputToolbarView.xib in Resources */, B1DCC61722E5E17100625807 /* EmojiPickerViewController.storyboard in Resources */, 32891D6C2264CBA300C82226 /* SimpleScreenTemplateViewController.storyboard in Resources */, + 32D5D16123E1EE2700E3E37C /* ManageSession.storyboard in Resources */, B1963B2C228F1C4900CBA17F /* BubbleReactionViewCell.xib in Resources */, B1664DA320F4F96200808783 /* Vector.strings in Resources */, B1B557C720EF5CD400210D55 /* DirectoryServerDetailTableViewCell.xib in Resources */, @@ -4753,6 +4770,7 @@ B1DCC62822E60CE300625807 /* EmojiCategory.swift in Sources */, B14084CC23BF9DE90010F692 /* KeyVerificationConclusionWithPaginationTitleBubbleCell.swift in Sources */, B1B5578F20EF568D00210D55 /* GroupTableViewCell.m in Sources */, + 32D5D16023E1EE2700E3E37C /* ManageSessionViewController.m in Sources */, B1B5573220EE6C4D00210D55 /* GroupHomeViewController.m in Sources */, B1B5595220EF9A8700210D55 /* RecentTableViewCell.m in Sources */, 32F6B96C2270623100BBA352 /* DeviceVerificationDataLoadingCoordinatorType.swift in Sources */, diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index c82e8a04c2..359af0327d 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -557,6 +557,15 @@ "security_settings_export_keys_manually" = "Export keys manually"; +// Manage session +"manage_session_title" = "Manage session"; +"manage_session_info" = "SESSION INFO"; +"manage_session_name" = "Device name"; +"manage_session_trusted" = "Trusted by you"; +"manage_session_not_trusted" = "Not trusted"; +"manage_session_sign_out" = "Sign out of this device"; + + // Identity server settings "identity_server_settings_title" = "Identity Server"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 2a7d5a0bd4..7a209d8354 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -1534,6 +1534,30 @@ internal enum VectorL10n { internal static var leave: String { return VectorL10n.tr("Vector", "leave") } + /// SESSION INFO + internal static var manageSessionInfo: String { + return VectorL10n.tr("Vector", "manage_session_info") + } + /// Device name + internal static var manageSessionName: String { + return VectorL10n.tr("Vector", "manage_session_name") + } + /// Not trusted + internal static var manageSessionNotTrusted: String { + return VectorL10n.tr("Vector", "manage_session_not_trusted") + } + /// Sign out of this device + internal static var manageSessionSignOut: String { + return VectorL10n.tr("Vector", "manage_session_sign_out") + } + /// Manage session + internal static var manageSessionTitle: String { + return VectorL10n.tr("Vector", "manage_session_title") + } + /// Trusted by you + internal static var manageSessionTrusted: String { + return VectorL10n.tr("Vector", "manage_session_trusted") + } /// Library internal static var mediaPickerLibrary: String { return VectorL10n.tr("Vector", "media_picker_library") diff --git a/Riot/Modules/Settings/Security/ManageSession/ManageSession.storyboard b/Riot/Modules/Settings/Security/ManageSession/ManageSession.storyboard new file mode 100644 index 0000000000..3292716a41 --- /dev/null +++ b/Riot/Modules/Settings/Security/ManageSession/ManageSession.storyboard @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.h b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.h new file mode 100644 index 0000000000..001ceefc05 --- /dev/null +++ b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.h @@ -0,0 +1,27 @@ +/* + Copyright 2020 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 + +#import "DeviceView.h" + + +@interface ManageSessionViewController : MXKTableViewController + ++ (ManageSessionViewController*)instantiateWithMatrixSession:(MXSession*)matrixSession andDevice:(MXDevice*)device; + +@end + diff --git a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m new file mode 100644 index 0000000000..e10d9c3e8a --- /dev/null +++ b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m @@ -0,0 +1,691 @@ +/* + Copyright 2020 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 "ManageSessionViewController.h" + +#import + +#import + +#import "AppDelegate.h" +#import "AvatarGenerator.h" + +#import "ThemeService.h" + +#import "Riot-Swift.h" + + +enum +{ + SECTION_SESSION_INFO, + SECTION_ACTION, + SECTION_COUNT +}; + +enum { + SESSION_INFO_SESSION_NAME, + SESSION_INFO_TRUST, + SESSION_INFO_COUNT +}; + +enum { + ACTION_REMOVE_SESSION, + ACTION_COUNT +}; + + +@interface ManageSessionViewController () < +MXKDataSourceDelegate, +MXKDeviceViewDelegate, +MXKEncryptionInfoViewDelegate> +{ + // The device to display + MXDevice *device; + + // Current alert (if any). + UIAlertController *currentAlert; + + DeviceView *deviceView; + + // Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar. + id kAppDelegateDidTapStatusBarNotificationObserver; + + // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. + id kThemeServiceDidChangeThemeNotificationObserver; + + // The current pushed view controller + UIViewController *pushedViewController; +} + +@end + +@implementation ManageSessionViewController + +#pragma mark - Setup & Teardown + ++ (ManageSessionViewController*)instantiateWithMatrixSession:(MXSession*)matrixSession andDevice:(MXDevice*)device; +{ + ManageSessionViewController* viewController = [[UIStoryboard storyboardWithName:@"ManageSession" bundle:[NSBundle mainBundle]] instantiateInitialViewController]; + [viewController addMatrixSession:matrixSession]; + viewController->device = device; + return viewController; +} + + +#pragma mark - View life cycle + +- (void)finalizeInit +{ + [super finalizeInit]; + + // Setup `MXKViewControllerHandling` properties + self.enableBarTintColorStatusChange = NO; + self.rageShakeManager = [RageShakeManager sharedManager]; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + // Do any additional setup after loading the view, typically from a nib. + + self.navigationItem.title = NSLocalizedStringFromTable(@"manage_session_title", @"Vector", nil); + + // Remove back bar button title when pushing a view controller + self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; + + [self.tableView registerClass:MXKTableViewCellWithLabelAndTextField.class forCellReuseIdentifier:[MXKTableViewCellWithLabelAndTextField defaultReuseIdentifier]]; + [self.tableView registerClass:MXKTableViewCellWithLabelAndSwitch.class forCellReuseIdentifier:[MXKTableViewCellWithLabelAndSwitch defaultReuseIdentifier]]; + [self.tableView registerNib:MXKTableViewCellWithTextView.nib forCellReuseIdentifier:[MXKTableViewCellWithTextView defaultReuseIdentifier]]; + + // Enable self sizing cells + self.tableView.rowHeight = UITableViewAutomaticDimension; + self.tableView.estimatedRowHeight = 50; + + // Observe user interface theme change. + kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + + [self userInterfaceThemeDidChange]; + + }]; + [self userInterfaceThemeDidChange]; +} + +- (void)userInterfaceThemeDidChange +{ + [ThemeService.shared.theme applyStyleOnNavigationBar:self.navigationController.navigationBar]; + + self.activityIndicator.backgroundColor = ThemeService.shared.theme.overlayBackgroundColor; + + // Check the table view style to select its bg color. + self.tableView.backgroundColor = ((self.tableView.style == UITableViewStylePlain) ? ThemeService.shared.theme.backgroundColor : ThemeService.shared.theme.headerBackgroundColor); + self.view.backgroundColor = self.tableView.backgroundColor; + self.tableView.separatorColor = ThemeService.shared.theme.lineBreakColor; + + if (self.tableView.dataSource) + { + [self refreshSettings]; + } +} + +- (UIStatusBarStyle)preferredStatusBarStyle +{ + return ThemeService.shared.theme.statusBarStyle; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +- (void)destroy +{ + // Release the potential pushed view controller + [self releasePushedViewController]; + + if (kThemeServiceDidChangeThemeNotificationObserver) + { + [[NSNotificationCenter defaultCenter] removeObserver:kThemeServiceDidChangeThemeNotificationObserver]; + kThemeServiceDidChangeThemeNotificationObserver = nil; + } +} + +- (void)onMatrixSessionStateDidChange:(NSNotification *)notif +{ + MXSession *mxSession = notif.object; + + // Check whether the concerned session is a new one which is not already associated with this view controller. + if (mxSession.state == MXSessionStateInitialised && [self.mxSessions indexOfObject:mxSession] != NSNotFound) + { + // Store this new session + [self addMatrixSession:mxSession]; + } + else + { + [super onMatrixSessionStateDidChange:notif]; + } +} + +- (void)viewWillAppear:(BOOL)animated +{ + [super viewWillAppear:animated]; + + // Screen tracking + [[Analytics sharedInstance] trackScreen:@"Settings"]; + + // Release the potential pushed view controller + [self releasePushedViewController]; + + // Refresh display + [self refreshSettings]; + + // Observe kAppDelegateDidTapStatusBarNotificationObserver. + kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + + [self.tableView setContentOffset:CGPointMake(-self.tableView.mxk_adjustedContentInset.left, -self.tableView.mxk_adjustedContentInset.top) animated:YES]; + + }]; +} + +- (void)viewWillDisappear:(BOOL)animated +{ + [super viewWillDisappear:animated]; + + if (currentAlert) + { + [currentAlert dismissViewControllerAnimated:NO completion:nil]; + currentAlert = nil; + } + + if (kAppDelegateDidTapStatusBarNotificationObserver) + { + [[NSNotificationCenter defaultCenter] removeObserver:kAppDelegateDidTapStatusBarNotificationObserver]; + kAppDelegateDidTapStatusBarNotificationObserver = nil; + } +} + +#pragma mark - Internal methods + +- (void)pushViewController:(UIViewController*)viewController +{ + // Keep ref on pushed view controller + pushedViewController = viewController; + + // Hide back button title + self.navigationItem.backBarButtonItem =[[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; + + [self.navigationController pushViewController:viewController animated:YES]; +} + +- (void)releasePushedViewController +{ + if (pushedViewController) + { + if ([pushedViewController isKindOfClass:[UINavigationController class]]) + { + UINavigationController *navigationController = (UINavigationController*)pushedViewController; + for (id subViewController in navigationController.viewControllers) + { + if ([subViewController respondsToSelector:@selector(destroy)]) + { + [subViewController destroy]; + } + } + } + else if ([pushedViewController respondsToSelector:@selector(destroy)]) + { + [(id)pushedViewController destroy]; + } + + pushedViewController = nil; + } +} + +- (void)reset +{ + // Remove observers + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + if (deviceView) + { + [deviceView removeFromSuperview]; + deviceView = nil; + } +} + +- (void)showDeviceDetails:(MXDevice *)device +{ + deviceView = [[DeviceView alloc] initWithDevice:device andMatrixSession:self.mainSession]; + deviceView.delegate = self; + + // Add the view and define edge constraints + [self.tableView.superview addSubview:deviceView]; + [self.tableView.superview bringSubviewToFront:deviceView]; + + NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:deviceView + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:self.tableView + attribute:NSLayoutAttributeTop + multiplier:1.0f + constant:0.0f]; + + NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:deviceView + attribute:NSLayoutAttributeLeft + relatedBy:NSLayoutRelationEqual + toItem:self.tableView + attribute:NSLayoutAttributeLeft + multiplier:1.0f + constant:0.0f]; + + NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:deviceView + attribute:NSLayoutAttributeWidth + relatedBy:NSLayoutRelationEqual + toItem:self.tableView + attribute:NSLayoutAttributeWidth + multiplier:1.0f + constant:0.0f]; + + NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:deviceView + attribute:NSLayoutAttributeHeight + relatedBy:NSLayoutRelationEqual + toItem:self.tableView + attribute:NSLayoutAttributeHeight + multiplier:1.0f + constant:0.0f]; + + [NSLayoutConstraint activateConstraints:@[topConstraint, leftConstraint, widthConstraint, heightConstraint]]; +} + +- (void)deviceView:(DeviceView*)theDeviceView presentAlertController:(UIAlertController *)alert +{ + [self presentViewController:alert animated:YES completion:nil]; +} + +- (void)dismissDeviceView:(MXKDeviceView *)theDeviceView didUpdate:(BOOL)isUpdated +{ + [deviceView removeFromSuperview]; + deviceView = nil; +} + +- (void)refreshSettings +{ + // Trigger a full table reloadData + [self.tableView reloadData]; +} + +- (void)requestAccountPasswordWithTitle:(NSString*)title message:(NSString*)message onComplete:(void (^)(NSString *password))onComplete +{ + [currentAlert dismissViewControllerAnimated:NO completion:nil]; + + // Prompt the user before deleting the device. + currentAlert = [UIAlertController alertControllerWithTitle:title + message:message + preferredStyle:UIAlertControllerStyleAlert]; + + [currentAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.secureTextEntry = YES; + textField.placeholder = nil; + textField.keyboardType = UIKeyboardTypeDefault; + }]; + + MXWeakify(self); + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) + { + MXStrongifyAndReturnIfNil(self); + self->currentAlert = nil; + }]]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"continue"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + MXStrongifyAndReturnIfNil(self); + + UITextField *textField = [self->currentAlert textFields].firstObject; + self->currentAlert = nil; + + onComplete(textField.text); + }]]; + + [self presentViewController:currentAlert animated:YES completion:nil]; +} + +#pragma mark - Segues + +- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender +{ + // Keep ref on destinationViewController + [super prepareForSegue:segue sender:sender]; + + // FIXME add night mode +} + +#pragma mark - UITableView data source + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + return SECTION_COUNT; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + NSInteger count = 0; + + switch (section) + { + case SECTION_SESSION_INFO: + count = SESSION_INFO_COUNT; + break; + case SECTION_ACTION: + count = ACTION_COUNT; + break; + } + + return count; +} + +- (MXKTableViewCellWithLabelAndTextField*)getLabelAndTextFieldCell:(UITableView*)tableview forIndexPath:(NSIndexPath *)indexPath +{ + MXKTableViewCellWithLabelAndTextField *cell = [tableview dequeueReusableCellWithIdentifier:[MXKTableViewCellWithLabelAndTextField defaultReuseIdentifier] forIndexPath:indexPath]; + + cell.mxkLabelLeadingConstraint.constant = cell.separatorInset.left; + cell.mxkTextFieldLeadingConstraint.constant = 16; + cell.mxkTextFieldTrailingConstraint.constant = 15; + + cell.mxkLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + + cell.mxkTextField.userInteractionEnabled = YES; + cell.mxkTextField.borderStyle = UITextBorderStyleNone; + cell.mxkTextField.textAlignment = NSTextAlignmentRight; + cell.mxkTextField.textColor = ThemeService.shared.theme.textSecondaryColor; + cell.mxkTextField.font = [UIFont systemFontOfSize:16]; + cell.mxkTextField.placeholder = nil; + + cell.accessoryType = UITableViewCellAccessoryNone; + cell.accessoryView = nil; + + cell.alpha = 1.0f; + cell.userInteractionEnabled = YES; + + [cell layoutIfNeeded]; + + return cell; +} + +- (MXKTableViewCellWithLabelAndSwitch*)getLabelAndSwitchCell:(UITableView*)tableview forIndexPath:(NSIndexPath *)indexPath +{ + MXKTableViewCellWithLabelAndSwitch *cell = [tableview dequeueReusableCellWithIdentifier:[MXKTableViewCellWithLabelAndSwitch defaultReuseIdentifier] forIndexPath:indexPath]; + + cell.mxkLabelLeadingConstraint.constant = cell.separatorInset.left; + cell.mxkSwitchTrailingConstraint.constant = 15; + + cell.mxkLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + + [cell.mxkSwitch removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; + + // Force layout before reusing a cell (fix switch displayed outside the screen) + [cell layoutIfNeeded]; + + return cell; +} + +- (MXKTableViewCell*)getDefaultTableViewCell:(UITableView*)tableView +{ + MXKTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCell defaultReuseIdentifier]]; + if (!cell) + { + cell = [[MXKTableViewCell alloc] init]; + } + else + { + cell.selectionStyle = UITableViewCellSelectionStyleDefault; + cell.accessoryType = UITableViewCellAccessoryNone; + cell.accessoryView = nil; + cell.imageView.image = nil; + } + cell.textLabel.accessibilityIdentifier = nil; + cell.textLabel.font = [UIFont systemFontOfSize:17]; + cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + cell.contentView.backgroundColor = UIColor.clearColor; + + return cell; +} + +- (MXKTableViewCell*)trustCellWithDevice:(MXDevice*)device forTableView:(UITableView*)tableView +{ + MXKTableViewCell *cell = [self getDefaultTableViewCell:tableView]; + + NSString *deviceId = device.deviceId; + MXDeviceInfo *deviceInfo = [self.mainSession.crypto deviceWithDeviceId:deviceId ofUser:self.mainSession.myUser.userId]; + + cell.textLabel.numberOfLines = 0; + cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + + if (deviceInfo.trustLevel.isVerified) + { + cell.textLabel.text = NSLocalizedStringFromTable(@"manage_session_trusted", @"Vector", nil); + cell.imageView.image = [UIImage imageNamed:@"encryption_trusted"]; + } + else + { + cell.textLabel.text = NSLocalizedStringFromTable(@"manage_session_not_trusted", @"Vector", nil); + cell.imageView.image = [UIImage imageNamed:@"encryption_warning"]; + } + + return cell; +} + +- (MXKTableViewCell*)descriptionCellForTableView:(UITableView*)tableView withText:(NSString*)text +{ + MXKTableViewCell *cell = [self getDefaultTableViewCell:tableView]; + cell.textLabel.text = text; + cell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + cell.textLabel.numberOfLines = 0; + cell.contentView.backgroundColor = ThemeService.shared.theme.headerBackgroundColor; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + + return cell; +} + + +- (MXKTableViewCellWithTextView*)textViewCellForTableView:(UITableView*)tableView atIndexPath:(NSIndexPath *)indexPath +{ + MXKTableViewCellWithTextView *textViewCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithTextView defaultReuseIdentifier] forIndexPath:indexPath]; + + textViewCell.mxkTextView.textColor = ThemeService.shared.theme.textPrimaryColor; + textViewCell.mxkTextView.font = [UIFont systemFontOfSize:17]; + textViewCell.mxkTextView.backgroundColor = [UIColor clearColor]; + textViewCell.mxkTextViewLeadingConstraint.constant = tableView.separatorInset.left; + textViewCell.mxkTextViewTrailingConstraint.constant = tableView.separatorInset.right; + textViewCell.mxkTextView.accessibilityIdentifier = nil; + + return textViewCell; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + NSInteger section = indexPath.section; + NSInteger row = indexPath.row; + + // set the cell to a default value to avoid application crashes + UITableViewCell *cell = [[UITableViewCell alloc] init]; + cell.backgroundColor = [UIColor redColor]; + + MXSession* session = self.mainSession; + switch (section) + { + case SECTION_SESSION_INFO: + switch (row) + { + case SESSION_INFO_SESSION_NAME: + { + MXKTableViewCellWithLabelAndTextField *displaynameCell = [self getLabelAndTextFieldCell:tableView forIndexPath:indexPath]; + + displaynameCell.mxkLabel.text = NSLocalizedStringFromTable(@"manage_session_name", @"Vector", nil); + displaynameCell.mxkTextField.text = device.displayName; + displaynameCell.mxkTextField.userInteractionEnabled = NO; + + cell = displaynameCell; + break; + } + case SESSION_INFO_TRUST: + { + cell = [self trustCellWithDevice:device forTableView:tableView]; + } + + } + break; + + case SECTION_ACTION: + switch (row) + { + case ACTION_REMOVE_SESSION: + { + MXKTableViewCellWithButton *deactivateAccountBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; + + if (!deactivateAccountBtnCell) + { + deactivateAccountBtnCell = [[MXKTableViewCellWithButton alloc] init]; + } + else + { + // Fix https://github.com/vector-im/riot-ios/issues/1354 + deactivateAccountBtnCell.mxkButton.titleLabel.text = nil; + } + + NSString *btnTitle = NSLocalizedStringFromTable(@"manage_session_sign_out", @"Vector", nil); + [deactivateAccountBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; + [deactivateAccountBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; + [deactivateAccountBtnCell.mxkButton setTintColor:ThemeService.shared.theme.warningColor]; + deactivateAccountBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; + + [deactivateAccountBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; + //[deactivateAccountBtnCell.mxkButton addTarget:self action:@selector(deactivateAccountAction) forControlEvents:UIControlEventTouchUpInside]; + deactivateAccountBtnCell.mxkButton.accessibilityIdentifier = nil; + + cell = deactivateAccountBtnCell; + break; + } + } + break; + + } + + return cell; +} + +- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section +{ + switch (section) + { + case SECTION_SESSION_INFO: + return NSLocalizedStringFromTable(@"manage_session_info", @"Vector", nil); + case SECTION_ACTION: + return @""; + + } + + return nil; +} + +- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section +{ + if ([view isKindOfClass:UITableViewHeaderFooterView.class]) + { + // Customize label style + UITableViewHeaderFooterView *tableViewHeaderFooterView = (UITableViewHeaderFooterView*)view; + tableViewHeaderFooterView.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + tableViewHeaderFooterView.textLabel.font = [UIFont systemFontOfSize:15]; + } +} + + +#pragma mark - UITableView delegate + +- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath; +{ + cell.backgroundColor = ThemeService.shared.theme.backgroundColor; + + if (cell.selectionStyle != UITableViewCellSelectionStyleNone) + { + // Update the selected background view + if (ThemeService.shared.theme.selectedBackgroundColor) + { + cell.selectedBackgroundView = [[UIView alloc] init]; + cell.selectedBackgroundView.backgroundColor = ThemeService.shared.theme.selectedBackgroundColor; + } + else + { + if (tableView.style == UITableViewStylePlain) + { + cell.selectedBackgroundView = nil; + } + else + { + cell.selectedBackgroundView.backgroundColor = nil; + } + } + } +} + +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section +{ + if (section == SECTION_SESSION_INFO) + { + return 44; + } + return 24; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section +{ + if (section == SECTION_SESSION_INFO) + { + return 0; + } + return 24; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (self.tableView == tableView) + { + NSInteger section = indexPath.section; + NSInteger row = indexPath.row; + + if (section == SECTION_SESSION_INFO) + { + //[self showDeviceDetails:devicesArray[deviceIndex]]; + } + + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + } +} + +#pragma mark - actions + + +#pragma mark - MXKDataSourceDelegate + +- (void)dataSource:(MXKDataSource *)dataSource didCellChange:(id)changes +{ + // Group data has been updated. Do a simple full reload + [self refreshSettings]; +} + +@end diff --git a/Riot/Modules/Settings/Security/SecurtiyViewController.m b/Riot/Modules/Settings/Security/SecurtiyViewController.m index 83e2f55069..4d77918983 100644 --- a/Riot/Modules/Settings/Security/SecurtiyViewController.m +++ b/Riot/Modules/Settings/Security/SecurtiyViewController.m @@ -16,6 +16,8 @@ #import "SecurityViewController.h" +#import "ManageSessionViewController.h" + #import #import @@ -902,7 +904,9 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath NSUInteger deviceIndex = row; if (deviceIndex < devicesArray.count) { - [self showDeviceDetails:devicesArray[deviceIndex]]; + ManageSessionViewController *viewController = [ManageSessionViewController instantiateWithMatrixSession:self.mainSession andDevice:devicesArray[deviceIndex]]; + + [self pushViewController:viewController]; } } From 0267098edec650dce45d4fc5b8d5038a46d93032 Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 29 Jan 2020 22:00:05 +0100 Subject: [PATCH 118/282] Settings > Security > Manage Session: Import rename code block from the kit --- .../ManageSessionViewController.m | 91 ++++++++++++++++++- 1 file changed, 87 insertions(+), 4 deletions(-) diff --git a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m index e10d9c3e8a..5d202031a8 100644 --- a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m +++ b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m @@ -327,6 +327,23 @@ - (void)refreshSettings [self.tableView reloadData]; } +- (void)reloadDeviceWithCompletion:(void (^)(void))completion +{ + MXWeakify(self); + [self.mainSession.matrixRestClient deviceByDeviceId:device.deviceId success:^(MXDevice *device) { + MXStrongifyAndReturnIfNil(self); + + self->device = device; + [self refreshSettings]; + completion(); + + } failure:^(NSError *error) { + NSLog(@"[ManageSessionVC] reloadDeviceWithCompletion failed. Error: %@", error); + [self refreshSettings]; + completion(); + }]; +} + - (void)requestAccountPasswordWithTitle:(NSString*)title message:(NSString*)message onComplete:(void (^)(NSString *password))onComplete { [currentAlert dismissViewControllerAnimated:NO completion:nil]; @@ -526,7 +543,6 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N UITableViewCell *cell = [[UITableViewCell alloc] init]; cell.backgroundColor = [UIColor redColor]; - MXSession* session = self.mainSession; switch (section) { case SECTION_SESSION_INFO: @@ -539,6 +555,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N displaynameCell.mxkLabel.text = NSLocalizedStringFromTable(@"manage_session_name", @"Vector", nil); displaynameCell.mxkTextField.text = device.displayName; displaynameCell.mxkTextField.userInteractionEnabled = NO; + displaynameCell.selectionStyle = UITableViewCellSelectionStyleDefault; cell = displaynameCell; break; @@ -667,10 +684,20 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath { NSInteger section = indexPath.section; NSInteger row = indexPath.row; - - if (section == SECTION_SESSION_INFO) + + switch (section) { - //[self showDeviceDetails:devicesArray[deviceIndex]]; + case SECTION_SESSION_INFO: + switch (row) + { + case SESSION_INFO_SESSION_NAME: + [self renameDevice]; + break; + + default: + break; + } + break; } [tableView deselectRowAtIndexPath:indexPath animated:YES]; @@ -679,6 +706,62 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath #pragma mark - actions +- (void)renameDevice +{ + // Prompt the user to enter a device name. + [currentAlert dismissViewControllerAnimated:NO completion:nil]; + + MXWeakify(self); + currentAlert = [UIAlertController alertControllerWithTitle:[NSBundle mxk_localizedStringForKey:@"device_details_rename_prompt_title"] + message:[NSBundle mxk_localizedStringForKey:@"device_details_rename_prompt_message"] preferredStyle:UIAlertControllerStyleAlert]; + + [currentAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) { + MXStrongifyAndReturnIfNil(self); + textField.secureTextEntry = NO; + textField.placeholder = nil; + textField.keyboardType = UIKeyboardTypeDefault; + textField.text = self->device.displayName; + }]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) + { + MXStrongifyAndReturnIfNil(self); + self->currentAlert = nil; + }]]; + + [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) + { + MXStrongifyAndReturnIfNil(self); + + NSString *text = [self->currentAlert textFields].firstObject.text; + self->currentAlert = nil; + + + // Hot change + self->device.displayName = text; + [self refreshSettings]; + [self.activityIndicator startAnimating]; + + [self.mainSession.matrixRestClient setDeviceName:text forDeviceId:self->device.deviceId success:^{ + [self reloadDeviceWithCompletion:^{ + [self.activityIndicator stopAnimating]; + }]; + } failure:^(NSError *error) { + + NSLog(@"[ManageSessionVC] Rename device (%@) failed", self->device.deviceId); + [self reloadDeviceWithCompletion:^{ + [self.activityIndicator stopAnimating]; + }]; + }]; + + }]]; + + [self presentViewController:currentAlert animated:YES completion:nil]; +} #pragma mark - MXKDataSourceDelegate From 27149e06ef25d32e1c45db5d4320268617c55615 Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 29 Jan 2020 23:17:57 +0100 Subject: [PATCH 119/282] Settings > Security > Manage Session: Import delete device code block from the kit --- .../ManageSessionViewController.m | 194 +++++++++++------- 1 file changed, 123 insertions(+), 71 deletions(-) diff --git a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m index 5d202031a8..11e4b1372d 100644 --- a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m +++ b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m @@ -266,61 +266,6 @@ - (void)reset } } -- (void)showDeviceDetails:(MXDevice *)device -{ - deviceView = [[DeviceView alloc] initWithDevice:device andMatrixSession:self.mainSession]; - deviceView.delegate = self; - - // Add the view and define edge constraints - [self.tableView.superview addSubview:deviceView]; - [self.tableView.superview bringSubviewToFront:deviceView]; - - NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:deviceView - attribute:NSLayoutAttributeTop - relatedBy:NSLayoutRelationEqual - toItem:self.tableView - attribute:NSLayoutAttributeTop - multiplier:1.0f - constant:0.0f]; - - NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:deviceView - attribute:NSLayoutAttributeLeft - relatedBy:NSLayoutRelationEqual - toItem:self.tableView - attribute:NSLayoutAttributeLeft - multiplier:1.0f - constant:0.0f]; - - NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:deviceView - attribute:NSLayoutAttributeWidth - relatedBy:NSLayoutRelationEqual - toItem:self.tableView - attribute:NSLayoutAttributeWidth - multiplier:1.0f - constant:0.0f]; - - NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:deviceView - attribute:NSLayoutAttributeHeight - relatedBy:NSLayoutRelationEqual - toItem:self.tableView - attribute:NSLayoutAttributeHeight - multiplier:1.0f - constant:0.0f]; - - [NSLayoutConstraint activateConstraints:@[topConstraint, leftConstraint, widthConstraint, heightConstraint]]; -} - -- (void)deviceView:(DeviceView*)theDeviceView presentAlertController:(UIAlertController *)alert -{ - [self presentViewController:alert animated:YES completion:nil]; -} - -- (void)dismissDeviceView:(MXKDeviceView *)theDeviceView didUpdate:(BOOL)isUpdated -{ - [deviceView removeFromSuperview]; - deviceView = nil; -} - - (void)refreshSettings { // Trigger a full table reloadData @@ -573,29 +518,27 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N { case ACTION_REMOVE_SESSION: { - MXKTableViewCellWithButton *deactivateAccountBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; + MXKTableViewCellWithButton *removeSessionBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; - if (!deactivateAccountBtnCell) + if (!removeSessionBtnCell) { - deactivateAccountBtnCell = [[MXKTableViewCellWithButton alloc] init]; + removeSessionBtnCell = [[MXKTableViewCellWithButton alloc] init]; } else { // Fix https://github.com/vector-im/riot-ios/issues/1354 - deactivateAccountBtnCell.mxkButton.titleLabel.text = nil; + removeSessionBtnCell.mxkButton.titleLabel.text = nil; } NSString *btnTitle = NSLocalizedStringFromTable(@"manage_session_sign_out", @"Vector", nil); - [deactivateAccountBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; - [deactivateAccountBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; - [deactivateAccountBtnCell.mxkButton setTintColor:ThemeService.shared.theme.warningColor]; - deactivateAccountBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; - - [deactivateAccountBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; - //[deactivateAccountBtnCell.mxkButton addTarget:self action:@selector(deactivateAccountAction) forControlEvents:UIControlEventTouchUpInside]; - deactivateAccountBtnCell.mxkButton.accessibilityIdentifier = nil; + [removeSessionBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; + [removeSessionBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; + [removeSessionBtnCell.mxkButton setTintColor:ThemeService.shared.theme.warningColor]; + removeSessionBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; + removeSessionBtnCell.mxkButton.userInteractionEnabled = NO; + removeSessionBtnCell.selectionStyle = UITableViewCellSelectionStyleDefault; - cell = deactivateAccountBtnCell; + cell = removeSessionBtnCell; break; } } @@ -693,13 +636,23 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath case SESSION_INFO_SESSION_NAME: [self renameDevice]; break; - - default: + case SESSION_INFO_TRUST: + [self showTrustForDevice:device]; break; } break; + + case SECTION_ACTION: + { + switch (row) + { + case ACTION_REMOVE_SESSION: + [self removeDevice]; + break; + } + } } - + [tableView deselectRowAtIndexPath:indexPath animated:YES]; } } @@ -755,6 +708,7 @@ - (void)renameDevice NSLog(@"[ManageSessionVC] Rename device (%@) failed", self->device.deviceId); [self reloadDeviceWithCompletion:^{ [self.activityIndicator stopAnimating]; + [[AppDelegate theDelegate] showErrorAsAlert:error]; }]; }]; @@ -763,6 +717,104 @@ - (void)renameDevice [self presentViewController:currentAlert animated:YES completion:nil]; } +- (void)showTrustForDevice:(MXDevice *)device +{ + [[AppDelegate theDelegate] showAlertWithTitle:@"Device Trust" message:@"TODO with bottom sheet 😛"]; +} + +- (void)removeDevice +{ + // Get an authentication session to prepare device deletion + [self.activityIndicator startAnimating]; + + MXWeakify(self); + [self.mainSession.matrixRestClient getSessionToDeleteDeviceByDeviceId:device.deviceId success:^(MXAuthenticationSession *authSession) { + MXStrongifyAndReturnIfNil(self); + + // Check whether the password based type is supported + BOOL isPasswordBasedTypeSupported = NO; + for (MXLoginFlow *loginFlow in authSession.flows) + { + if ([loginFlow.type isEqualToString:kMXLoginFlowTypePassword] || [loginFlow.stages indexOfObject:kMXLoginFlowTypePassword] != NSNotFound) + { + isPasswordBasedTypeSupported = YES; + break; + } + } + + if (isPasswordBasedTypeSupported && authSession.session) + { + // Prompt for a password + [self->currentAlert dismissViewControllerAnimated:NO completion:nil]; + + // Prompt the user before deleting the device. + self->currentAlert = [UIAlertController alertControllerWithTitle:[NSBundle mxk_localizedStringForKey:@"device_details_delete_prompt_title"] message:[NSBundle mxk_localizedStringForKey:@"device_details_delete_prompt_message"] preferredStyle:UIAlertControllerStyleAlert]; + + + [self->currentAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) { + + textField.secureTextEntry = YES; + textField.placeholder = nil; + textField.keyboardType = UIKeyboardTypeDefault; + }]; + + [self->currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) + { + self->currentAlert = nil; + [self.activityIndicator stopAnimating]; + }]]; + + [self->currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"submit"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) + { + + UITextField *textField = [self->currentAlert textFields].firstObject; + self->currentAlert = nil; + + NSString *userId = self.mainSession.myUser.userId; + NSDictionary *authParams; + + // Sanity check + if (userId) + { + authParams = @{@"session":authSession.session, + @"user": userId, + @"password": textField.text, + @"type": kMXLoginFlowTypePassword}; + + } + + [self.mainSession.matrixRestClient deleteDeviceByDeviceId:self->device.deviceId authParams:authParams success:^{ + [self.activityIndicator stopAnimating]; + + // We cannot stay in this screen anymore + [self withdrawViewControllerAnimated:YES completion:nil]; + } failure:^(NSError *error) { + NSLog(@"[ManageSessionVC] Delete device (%@) failed", self->device.deviceId); + [self.activityIndicator stopAnimating]; + [[AppDelegate theDelegate] showErrorAsAlert:error]; + }]; + }]]; + + [self presentViewController:self->currentAlert animated:YES completion:nil]; + } + else + { + NSLog(@"[ManageSessionVC] Delete device (%@) failed, auth session flow type is not supported", self->device.deviceId); + [self.activityIndicator stopAnimating]; + //[[AppDelegate theDelegate] showErrorAsAlert:error]; + } + + } failure:^(NSError *error) { + NSLog(@"[ManageSessionVC] Delete device (%@) failed, unable to get auth session", self->device.deviceId); + [self.activityIndicator stopAnimating]; + [[AppDelegate theDelegate] showErrorAsAlert:error]; + }]; +} + #pragma mark - MXKDataSourceDelegate - (void)dataSource:(MXKDataSource *)dataSource didCellChange:(id)changes From af0a45ef54ba16750b67dee1864d11b7521defb7 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 30 Jan 2020 06:52:20 +0100 Subject: [PATCH 120/282] Settings: Remove code for things that have their own screen now --- .../Security/SecurtiyViewController.m | 107 ------------------ 1 file changed, 107 deletions(-) diff --git a/Riot/Modules/Settings/Security/SecurtiyViewController.m b/Riot/Modules/Settings/Security/SecurtiyViewController.m index 4d77918983..694c0ecbf5 100644 --- a/Riot/Modules/Settings/Security/SecurtiyViewController.m +++ b/Riot/Modules/Settings/Security/SecurtiyViewController.m @@ -56,9 +56,7 @@ @interface SecurityViewController () < MXKDataSourceDelegate, -MXKDeviceViewDelegate, SettingsKeyBackupTableViewSectionDelegate, -MXKEncryptionInfoViewDelegate, KeyBackupSetupCoordinatorBridgePresenterDelegate, KeyBackupRecoverCoordinatorBridgePresenterDelegate, UIDocumentInteractionControllerDelegate> @@ -68,7 +66,6 @@ @interface SecurityViewController () < // Devices NSMutableArray *devicesArray; - DeviceView *deviceView; // Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar. id kAppDelegateDidTapStatusBarNotificationObserver; @@ -306,12 +303,6 @@ - (void)reset { // Remove observers [[NSNotificationCenter defaultCenter] removeObserver:self]; - - if (deviceView) - { - [deviceView removeFromSuperview]; - deviceView = nil; - } } - (void)loadCurrentDeviceInformation @@ -452,110 +443,12 @@ - (void)loadDevices }]; } -- (void)showDeviceDetails:(MXDevice *)device -{ - deviceView = [[DeviceView alloc] initWithDevice:device andMatrixSession:self.mainSession]; - deviceView.delegate = self; - - // Add the view and define edge constraints - [self.tableView.superview addSubview:deviceView]; - [self.tableView.superview bringSubviewToFront:deviceView]; - - NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:deviceView - attribute:NSLayoutAttributeTop - relatedBy:NSLayoutRelationEqual - toItem:self.tableView - attribute:NSLayoutAttributeTop - multiplier:1.0f - constant:0.0f]; - - NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:deviceView - attribute:NSLayoutAttributeLeft - relatedBy:NSLayoutRelationEqual - toItem:self.tableView - attribute:NSLayoutAttributeLeft - multiplier:1.0f - constant:0.0f]; - - NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:deviceView - attribute:NSLayoutAttributeWidth - relatedBy:NSLayoutRelationEqual - toItem:self.tableView - attribute:NSLayoutAttributeWidth - multiplier:1.0f - constant:0.0f]; - - NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:deviceView - attribute:NSLayoutAttributeHeight - relatedBy:NSLayoutRelationEqual - toItem:self.tableView - attribute:NSLayoutAttributeHeight - multiplier:1.0f - constant:0.0f]; - - [NSLayoutConstraint activateConstraints:@[topConstraint, leftConstraint, widthConstraint, heightConstraint]]; -} - -- (void)deviceView:(DeviceView*)theDeviceView presentAlertController:(UIAlertController *)alert -{ - [self presentViewController:alert animated:YES completion:nil]; -} - -- (void)dismissDeviceView:(MXKDeviceView *)theDeviceView didUpdate:(BOOL)isUpdated -{ - [deviceView removeFromSuperview]; - deviceView = nil; - - if (isUpdated) - { - [self loadDevices]; - } -} - - (void)refreshSettings { // Trigger a full table reloadData [self.tableView reloadData]; } -- (void)requestAccountPasswordWithTitle:(NSString*)title message:(NSString*)message onComplete:(void (^)(NSString *password))onComplete -{ - [currentAlert dismissViewControllerAnimated:NO completion:nil]; - - // Prompt the user before deleting the device. - currentAlert = [UIAlertController alertControllerWithTitle:title - message:message - preferredStyle:UIAlertControllerStyleAlert]; - - [currentAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) { - textField.secureTextEntry = YES; - textField.placeholder = nil; - textField.keyboardType = UIKeyboardTypeDefault; - }]; - - MXWeakify(self); - [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) - { - MXStrongifyAndReturnIfNil(self); - self->currentAlert = nil; - }]]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"continue"] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - MXStrongifyAndReturnIfNil(self); - - UITextField *textField = [self->currentAlert textFields].firstObject; - self->currentAlert = nil; - - onComplete(textField.text); - }]]; - - [self presentViewController:currentAlert animated:YES completion:nil]; -} - #pragma mark - Segues - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender From 76541580a017896b9c838d40a183274a743395eb Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 30 Jan 2020 07:01:01 +0100 Subject: [PATCH 121/282] Settings: Remove code for things that have their own screen now --- .../Modules/Settings/SettingsViewController.h | 4 +- .../Modules/Settings/SettingsViewController.m | 587 +----------------- 2 files changed, 3 insertions(+), 588 deletions(-) diff --git a/Riot/Modules/Settings/SettingsViewController.h b/Riot/Modules/Settings/SettingsViewController.h index 4bce2072b8..096bd6241b 100644 --- a/Riot/Modules/Settings/SettingsViewController.h +++ b/Riot/Modules/Settings/SettingsViewController.h @@ -16,11 +16,9 @@ #import -#import "DeviceView.h" - #import "MediaPickerViewController.h" -@interface SettingsViewController : MXKTableViewController +@interface SettingsViewController : MXKTableViewController @end diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index dc32aeb6a9..1fbb18f386 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -64,9 +64,6 @@ SETTINGS_SECTION_ADVANCED_INDEX, SETTINGS_SECTION_OTHER_INDEX, SETTINGS_SECTION_LABS_INDEX, - SETTINGS_SECTION_CRYPTOGRAPHY_INDEX, - SETTINGS_SECTION_KEYBACKUP_INDEX, - SETTINGS_SECTION_DEVICES_INDEX, SETTINGS_SECTION_FLAIR_INDEX, SETTINGS_SECTION_DEACTIVATE_ACCOUNT_INDEX, SETTINGS_SECTION_COUNT @@ -142,18 +139,6 @@ LABS_COUNT }; -enum { - CRYPTOGRAPHY_INFO_INDEX = 0, - CRYPTOGRAPHY_BLACKLIST_UNVERIFIED_DEVICES_INDEX, - CRYPTOGRAPHY_EXPORT_INDEX, - CRYPTOGRAPHY_COUNT -}; - -enum -{ - DEVICES_DESCRIPTION_INDEX = 0 -}; - enum { SECURITY_BUTTON_INDEX = 0, @@ -166,10 +151,7 @@ @interface SettingsViewController () *devicesArray; - DeviceView *deviceView; - // Flair: the groups data source GroupsDataSource *groupsDataSource; @@ -249,14 +227,6 @@ @interface SettingsViewController () *devices) { - - if (devices) - { - devicesArray = [NSMutableArray arrayWithArray:devices]; - - // Sort devices according to the last seen date. - NSComparator comparator = ^NSComparisonResult(MXDevice *deviceA, MXDevice *deviceB) { - - if (deviceA.lastSeenTs > deviceB.lastSeenTs) - { - return NSOrderedAscending; - } - if (deviceA.lastSeenTs < deviceB.lastSeenTs) - { - return NSOrderedDescending; - } - - return NSOrderedSame; - }; - - // Sort devices list - [devicesArray sortUsingComparator:comparator]; - } - else - { - devicesArray = nil; - - } - - // Refresh all the table (A slide down animation is observed when we limit the refresh to the concerned section). - // Note: The use of 'reloadData' handles the case where the account has been logged out. - [self refreshSettings]; - - } failure:^(NSError *error) { - - // Display the data that has been loaded last time - // Note: The use of 'reloadData' handles the case where the account has been logged out. - [self refreshSettings]; - - }]; -} - -- (void)showDeviceDetails:(MXDevice *)device -{ - [self dismissKeyboard]; - - deviceView = [[DeviceView alloc] initWithDevice:device andMatrixSession:self.mainSession]; - deviceView.delegate = self; - - // Add the view and define edge constraints - [self.tableView.superview addSubview:deviceView]; - [self.tableView.superview bringSubviewToFront:deviceView]; - - NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:deviceView - attribute:NSLayoutAttributeTop - relatedBy:NSLayoutRelationEqual - toItem:self.tableView - attribute:NSLayoutAttributeTop - multiplier:1.0f - constant:0.0f]; - - NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:deviceView - attribute:NSLayoutAttributeLeft - relatedBy:NSLayoutRelationEqual - toItem:self.tableView - attribute:NSLayoutAttributeLeft - multiplier:1.0f - constant:0.0f]; - - NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:deviceView - attribute:NSLayoutAttributeWidth - relatedBy:NSLayoutRelationEqual - toItem:self.tableView - attribute:NSLayoutAttributeWidth - multiplier:1.0f - constant:0.0f]; - - NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:deviceView - attribute:NSLayoutAttributeHeight - relatedBy:NSLayoutRelationEqual - toItem:self.tableView - attribute:NSLayoutAttributeHeight - multiplier:1.0f - constant:0.0f]; - - [NSLayoutConstraint activateConstraints:@[topConstraint, leftConstraint, widthConstraint, heightConstraint]]; -} - -- (void)deviceView:(DeviceView*)theDeviceView presentAlertController:(UIAlertController *)alert -{ - [self dismissKeyboard]; - - [self presentViewController:alert animated:YES completion:nil]; -} - -- (void)dismissDeviceView:(MXKDeviceView *)theDeviceView didUpdate:(BOOL)isUpdated -{ - [deviceView removeFromSuperview]; - deviceView = nil; - - if (isUpdated) - { - [self loadDevices]; - } -} - - (void)editNewEmailTextField { if (newEmailTextField && ![newEmailTextField becomeFirstResponder]) @@ -1462,31 +1233,6 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger } } } - else if (section == SETTINGS_SECTION_DEVICES_INDEX) - { - count = devicesArray.count; - if (count) - { - // For some description (DEVICES_DESCRIPTION_INDEX) - count++; - } - } - else if (section == SETTINGS_SECTION_CRYPTOGRAPHY_INDEX) - { - // Check whether this section is visible. - if (self.mainSession.crypto) - { - count = CRYPTOGRAPHY_COUNT; - } - } - else if (section == SETTINGS_SECTION_KEYBACKUP_INDEX) - { - // Check whether this section is visible. - if (self.mainSession.crypto) - { - count = keyBackupSection.numberOfRows; - } - } else if (section == SETTINGS_SECTION_DEACTIVATE_ACCOUNT_INDEX) { count = 1; @@ -2480,94 +2226,6 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N [groupWithSwitchCell.toggleButton addTarget:self action:@selector(toggleCommunityFlair:) forControlEvents:UIControlEventTouchUpInside]; } } - else if (section == SETTINGS_SECTION_DEVICES_INDEX) - { - if (row == DEVICES_DESCRIPTION_INDEX) - { - MXKTableViewCell *descriptionCell = [self getDefaultTableViewCell:tableView]; - descriptionCell.textLabel.text = NSLocalizedStringFromTable(@"settings_devices_description", @"Vector", nil); - descriptionCell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; - descriptionCell.textLabel.font = [UIFont systemFontOfSize:15]; - descriptionCell.textLabel.numberOfLines = 0; - descriptionCell.contentView.backgroundColor = ThemeService.shared.theme.headerBackgroundColor; - descriptionCell.selectionStyle = UITableViewCellSelectionStyleNone; - - cell = descriptionCell; - } - else - { - NSUInteger deviceIndex = row - 1; - - MXKTableViewCell *deviceCell = [self getDefaultTableViewCell:tableView]; - if (deviceIndex < devicesArray.count) - { - NSString *name = devicesArray[deviceIndex].displayName; - NSString *deviceId = devicesArray[deviceIndex].deviceId; - deviceCell.textLabel.text = (name.length ? [NSString stringWithFormat:@"%@ (%@)", name, deviceId] : [NSString stringWithFormat:@"(%@)", deviceId]); - deviceCell.textLabel.numberOfLines = 0; - - if ([deviceId isEqualToString:self.mainSession.matrixRestClient.credentials.deviceId]) - { - deviceCell.textLabel.font = [UIFont boldSystemFontOfSize:17]; - } - } - - cell = deviceCell; - } - - } - else if (section == SETTINGS_SECTION_CRYPTOGRAPHY_INDEX) - { - if (row == CRYPTOGRAPHY_INFO_INDEX) - { - MXKTableViewCellWithTextView *cryptoCell = [self textViewCellForTableView:tableView atIndexPath:indexPath]; - - cryptoCell.mxkTextView.attributedText = [self cryptographyInformation]; - - cell = cryptoCell; - } - else if (row == CRYPTOGRAPHY_BLACKLIST_UNVERIFIED_DEVICES_INDEX) - { - MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - - labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_crypto_blacklist_unverified_devices", @"Vector", nil); - labelAndSwitchCell.mxkSwitch.on = account.mxSession.crypto.globalBlacklistUnverifiedDevices; - labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; - labelAndSwitchCell.mxkSwitch.enabled = YES; - [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleBlacklistUnverifiedDevices:) forControlEvents:UIControlEventTouchUpInside]; - - cell = labelAndSwitchCell; - } - else if (row == CRYPTOGRAPHY_EXPORT_INDEX) - { - MXKTableViewCellWithButton *exportKeysBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; - if (!exportKeysBtnCell) - { - exportKeysBtnCell = [[MXKTableViewCellWithButton alloc] init]; - } - else - { - // Fix https://github.com/vector-im/riot-ios/issues/1354 - exportKeysBtnCell.mxkButton.titleLabel.text = nil; - } - - NSString *btnTitle = NSLocalizedStringFromTable(@"settings_crypto_export", @"Vector", nil); - [exportKeysBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; - [exportKeysBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; - [exportKeysBtnCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; - exportKeysBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; - - [exportKeysBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; - [exportKeysBtnCell.mxkButton addTarget:self action:@selector(exportEncryptionKeys:) forControlEvents:UIControlEventTouchUpInside]; - exportKeysBtnCell.mxkButton.accessibilityIdentifier = nil; - - cell = exportKeysBtnCell; - } - } - else if (section == SETTINGS_SECTION_KEYBACKUP_INDEX) - { - cell = [keyBackupSection cellForRowAtRow:row]; - } else if (section == SETTINGS_SECTION_SECURITY_INDEX) { switch (row) @@ -2675,30 +2333,6 @@ - (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSectio return NSLocalizedStringFromTable(@"settings_flair", @"Vector", nil); } } - else if (section == SETTINGS_SECTION_DEVICES_INDEX) - { - // Check whether this section is visible - if (devicesArray.count > 0) - { - return NSLocalizedStringFromTable(@"settings_devices", @"Vector", nil); - } - } - else if (section == SETTINGS_SECTION_CRYPTOGRAPHY_INDEX) - { - // Check whether this section is visible - if (self.mainSession.crypto) - { - return NSLocalizedStringFromTable(@"settings_cryptography", @"Vector", nil); - } - } - else if (section == SETTINGS_SECTION_KEYBACKUP_INDEX) - { - // Check whether this section is visible - if (self.mainSession.crypto) - { - return NSLocalizedStringFromTable(@"settings_key_backup", @"Vector", nil); - } - } else if (section == SETTINGS_SECTION_SECURITY_INDEX) { return NSLocalizedStringFromTable(@"settings_security", @"Vector", nil); @@ -3027,28 +2661,6 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath } } } - else if (section == SETTINGS_SECTION_DEVICES_INDEX) - { - if (row > DEVICES_DESCRIPTION_INDEX) - { - NSUInteger deviceIndex = row - 1; - if (deviceIndex < devicesArray.count) - { - [self showDeviceDetails:devicesArray[deviceIndex]]; - } - } - } - else if (section == SETTINGS_SECTION_CONTACTS_INDEX) - { - if (row == localContactsPhoneBookCountryIndex) - { - CountryPickerViewController *countryPicker = [CountryPickerViewController countryPickerViewController]; - countryPicker.view.tag = SETTINGS_SECTION_CONTACTS_INDEX; - countryPicker.delegate = self; - countryPicker.showCountryCallingCode = YES; - [self pushViewController:countryPicker]; - } - } else if (section == SETTINGS_SECTION_SECURITY_INDEX) { switch (row) @@ -3435,16 +3047,6 @@ - (void)toggleLabsCrossSigning:(id)sender RiotSettings.shared.enableCrossSigning = switchButton.isOn; } -- (void)toggleBlacklistUnverifiedDevices:(id)sender -{ - UISwitch *switchButton = (UISwitch*)sender; - - MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; - account.mxSession.crypto.globalBlacklistUnverifiedDevices = switchButton.on; - - [self.tableView reloadData]; -} - - (void)togglePinRoomsWithMissedNotif:(id)sender { UISwitch *switchButton = (UISwitch*)sender; @@ -4041,69 +3643,6 @@ - (void)onProfileAvatarTap:(UITapGestureRecognizer *)recognizer self.imagePickerPresenter = singleImagePickerPresenter; } -- (void)exportEncryptionKeys:(UITapGestureRecognizer *)recognizer -{ - [currentAlert dismissViewControllerAnimated:NO completion:nil]; - - exportView = [[MXKEncryptionKeysExportView alloc] initWithMatrixSession:self.mainSession]; - currentAlert = exportView.alertController; - - // Use a temporary file for the export - keyExportsFile = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"riot-keys.txt"]]; - - // Make sure the file is empty - [self deleteKeyExportFile]; - - // Show the export dialog - __weak typeof(self) weakSelf = self; - [exportView showInViewController:self toExportKeysToFile:keyExportsFile onComplete:^(BOOL success) { - - if (weakSelf) - { - typeof(self) self = weakSelf; - self->currentAlert = nil; - self->exportView = nil; - - if (success) - { - // Let another app handling this file - self->documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:keyExportsFile]; - [self->documentInteractionController setDelegate:self]; - - if ([self->documentInteractionController presentOptionsMenuFromRect:self.view.bounds inView:self.view animated:YES]) - { - // We want to delete the temp keys file after it has been processed by the other app. - // We use [UIDocumentInteractionControllerDelegate didEndSendingToApplication] for that - // but it is not reliable for all cases (see http://stackoverflow.com/a/21867096). - // So, arm a timer to auto delete the file after 10mins. - keyExportsFileDeletionTimer = [NSTimer scheduledTimerWithTimeInterval:600 target:self selector:@selector(deleteKeyExportFile) userInfo:self repeats:NO]; - } - else - { - self->documentInteractionController = nil; - [self deleteKeyExportFile]; - } - } - } - }]; -} - -- (void)deleteKeyExportFile -{ - // Cancel the deletion timer if it is still here - if (keyExportsFileDeletionTimer) - { - [keyExportsFileDeletionTimer invalidate]; - keyExportsFileDeletionTimer = nil; - } - - // And delete the file - if (keyExportsFile && [[NSFileManager defaultManager] fileExistsAtPath:keyExportsFile.path]) - { - [[NSFileManager defaultManager] removeItemAtPath:keyExportsFile.path error:nil]; - } -} - - (void)showThemePicker { __weak typeof(self) weakSelf = self; @@ -4465,18 +4004,6 @@ - (void)displayPasswordAlert [self presentViewController:resetPwdAlertController animated:YES completion:nil]; } -#pragma mark - UIDocumentInteractionControllerDelegate - -- (void)documentInteractionController:(UIDocumentInteractionController *)controller didEndSendingToApplication:(NSString *)application -{ - // If iOS wants to call this method, this is the right time to remove the file - [self deleteKeyExportFile]; -} - -- (void)documentInteractionControllerDidDismissOptionsMenu:(UIDocumentInteractionController *)controller -{ - documentInteractionController = nil; -} #pragma mark - MXKCountryPickerViewControllerDelegate @@ -4560,96 +4087,8 @@ - (void)deactivateAccountViewControllerDidCancel:(DeactivateAccountViewControlle [deactivateAccountViewController dismissViewControllerAnimated:YES completion:nil]; } -#pragma mark - SettingsKeyBackupTableViewSectionDelegate -- (void)settingsKeyBackupTableViewSectionDidUpdate:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection -{ - [self.tableView reloadData]; -} - -- (MXKTableViewCellWithTextView *)settingsKeyBackupTableViewSection:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection textCellForRow:(NSInteger)textCellForRow -{ - return [self textViewCellForTableView:self.tableView atIndexPath:[NSIndexPath indexPathForRow:textCellForRow inSection:SETTINGS_SECTION_KEYBACKUP_INDEX]]; -} - -- (MXKTableViewCellWithButton *)settingsKeyBackupTableViewSection:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection buttonCellForRow:(NSInteger)buttonCellForRow -{ - MXKTableViewCellWithButton *cell = [self.tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; - - if (!cell) - { - cell = [[MXKTableViewCellWithButton alloc] init]; - } - else - { - // Fix https://github.com/vector-im/riot-ios/issues/1354 - cell.mxkButton.titleLabel.text = nil; - } - - cell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; - [cell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; - - return cell; -} - -- (void)settingsKeyBackupTableViewSectionShowKeyBackupSetup:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection -{ - [self showKeyBackupSetupFromSignOutFlow:NO]; -} - -- (void)settingsKeyBackup:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection showKeyBackupRecover:(MXKeyBackupVersion *)keyBackupVersion -{ - [self showKeyBackupRecover:keyBackupVersion]; -} - -- (void)settingsKeyBackup:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection showKeyBackupDeleteConfirm:(MXKeyBackupVersion *)keyBackupVersion -{ - MXWeakify(self); - [currentAlert dismissViewControllerAnimated:NO completion:nil]; - - currentAlert = - [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"settings_key_backup_delete_confirmation_prompt_title", @"Vector", nil) - message:NSLocalizedStringFromTable(@"settings_key_backup_delete_confirmation_prompt_msg", @"Vector", nil) - preferredStyle:UIAlertControllerStyleAlert]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] - style:UIAlertActionStyleCancel - handler:^(UIAlertAction * action) { - MXStrongifyAndReturnIfNil(self); - self->currentAlert = nil; - }]]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"settings_key_backup_button_delete", @"Vector", nil) - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - MXStrongifyAndReturnIfNil(self); - self->currentAlert = nil; - - [self->keyBackupSection deleteWithKeyBackupVersion:keyBackupVersion]; - }]]; - - [currentAlert mxk_setAccessibilityIdentifier: @"SettingsVCDeleteKeyBackup"]; - [self presentViewController:currentAlert animated:YES completion:nil]; -} - -- (void)settingsKeyBackup:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection showActivityIndicator:(BOOL)show -{ - if (show) - { - [self startActivityIndicator]; - } - else - { - [self stopActivityIndicator]; - } -} - -- (void)settingsKeyBackup:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection showError:(NSError *)error -{ - [[AppDelegate theDelegate] showErrorAsAlert:error]; -} - -#pragma mark - KeyBackupRecoverCoordinatorBridgePresenter +#pragma mark - KeyBackupSetupCoordinatorBridgePresenter - (void)showKeyBackupSetupFromSignOutFlow:(BOOL)showFromSignOutFlow { @@ -4670,28 +4109,6 @@ - (void)keyBackupSetupCoordinatorBridgePresenterDelegateDidCancel:(KeyBackupSetu - (void)keyBackupSetupCoordinatorBridgePresenterDelegateDidSetupRecoveryKey:(KeyBackupSetupCoordinatorBridgePresenter *)bridgePresenter { [keyBackupSetupCoordinatorBridgePresenter dismissWithAnimated:true]; keyBackupSetupCoordinatorBridgePresenter = nil; - - [keyBackupSection reload]; -} - -#pragma mark - KeyBackupRecoverCoordinatorBridgePresenter - -- (void)showKeyBackupRecover:(MXKeyBackupVersion*)keyBackupVersion -{ - keyBackupRecoverCoordinatorBridgePresenter = [[KeyBackupRecoverCoordinatorBridgePresenter alloc] initWithSession:self.mainSession keyBackupVersion:keyBackupVersion]; - - [keyBackupRecoverCoordinatorBridgePresenter presentFrom:self animated:true]; - keyBackupRecoverCoordinatorBridgePresenter.delegate = self; -} - -- (void)keyBackupRecoverCoordinatorBridgePresenterDidCancel:(KeyBackupRecoverCoordinatorBridgePresenter *)bridgePresenter { - [keyBackupRecoverCoordinatorBridgePresenter dismissWithAnimated:true]; - keyBackupRecoverCoordinatorBridgePresenter = nil; -} - -- (void)keyBackupRecoverCoordinatorBridgePresenterDidRecover:(KeyBackupRecoverCoordinatorBridgePresenter *)bridgePresenter { - [keyBackupRecoverCoordinatorBridgePresenter dismissWithAnimated:true]; - keyBackupRecoverCoordinatorBridgePresenter = nil; } #pragma mark - SignOutAlertPresenterDelegate From 30359d42e059aa8c8f9c8efaf703508194a99819 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 30 Jan 2020 07:14:35 +0100 Subject: [PATCH 122/282] Settings: More cleaning --- .../ManageSessionViewController.m | 51 +++---------------- .../Security/SecurtiyViewController.m | 14 ++--- 2 files changed, 14 insertions(+), 51 deletions(-) diff --git a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m index 11e4b1372d..de16eed1d8 100644 --- a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m +++ b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m @@ -136,7 +136,7 @@ - (void)userInterfaceThemeDidChange if (self.tableView.dataSource) { - [self refreshSettings]; + [self reloadData]; } } @@ -190,7 +190,7 @@ - (void)viewWillAppear:(BOOL)animated [self releasePushedViewController]; // Refresh display - [self refreshSettings]; + [self reloadData]; // Observe kAppDelegateDidTapStatusBarNotificationObserver. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { @@ -266,7 +266,7 @@ - (void)reset } } -- (void)refreshSettings +- (void)reloadData { // Trigger a full table reloadData [self.tableView reloadData]; @@ -279,53 +279,16 @@ - (void)reloadDeviceWithCompletion:(void (^)(void))completion MXStrongifyAndReturnIfNil(self); self->device = device; - [self refreshSettings]; + [self reloadData]; completion(); } failure:^(NSError *error) { NSLog(@"[ManageSessionVC] reloadDeviceWithCompletion failed. Error: %@", error); - [self refreshSettings]; + [self reloadData]; completion(); }]; } -- (void)requestAccountPasswordWithTitle:(NSString*)title message:(NSString*)message onComplete:(void (^)(NSString *password))onComplete -{ - [currentAlert dismissViewControllerAnimated:NO completion:nil]; - - // Prompt the user before deleting the device. - currentAlert = [UIAlertController alertControllerWithTitle:title - message:message - preferredStyle:UIAlertControllerStyleAlert]; - - [currentAlert addTextFieldWithConfigurationHandler:^(UITextField *textField) { - textField.secureTextEntry = YES; - textField.placeholder = nil; - textField.keyboardType = UIKeyboardTypeDefault; - }]; - - MXWeakify(self); - [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) - { - MXStrongifyAndReturnIfNil(self); - self->currentAlert = nil; - }]]; - - [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"continue"] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - MXStrongifyAndReturnIfNil(self); - - UITextField *textField = [self->currentAlert textFields].firstObject; - self->currentAlert = nil; - - onComplete(textField.text); - }]]; - - [self presentViewController:currentAlert animated:YES completion:nil]; -} #pragma mark - Segues @@ -696,7 +659,7 @@ - (void)renameDevice // Hot change self->device.displayName = text; - [self refreshSettings]; + [self reloadData]; [self.activityIndicator startAnimating]; [self.mainSession.matrixRestClient setDeviceName:text forDeviceId:self->device.deviceId success:^{ @@ -820,7 +783,7 @@ - (void)removeDevice - (void)dataSource:(MXKDataSource *)dataSource didCellChange:(id)changes { // Group data has been updated. Do a simple full reload - [self refreshSettings]; + [self reloadData]; } @end diff --git a/Riot/Modules/Settings/Security/SecurtiyViewController.m b/Riot/Modules/Settings/Security/SecurtiyViewController.m index 694c0ecbf5..111fabae61 100644 --- a/Riot/Modules/Settings/Security/SecurtiyViewController.m +++ b/Riot/Modules/Settings/Security/SecurtiyViewController.m @@ -165,7 +165,7 @@ - (void)userInterfaceThemeDidChange if (self.tableView.dataSource) { - [self refreshSettings]; + [self reloadData]; } } @@ -229,7 +229,7 @@ - (void)viewWillAppear:(BOOL)animated [self releasePushedViewController]; // Refresh display - [self refreshSettings]; + [self reloadData]; // Refresh the current device information in parallel [self loadCurrentDeviceInformation]; @@ -313,7 +313,7 @@ - (void)loadCurrentDeviceInformation // Refresh all the table (A slide down animation is observed when we limit the refresh to the concerned section). // Note: The use of 'reloadData' handles the case where the account has been logged out. - [self refreshSettings]; + [self reloadData]; } failure:nil]; } @@ -432,18 +432,18 @@ - (void)loadDevices // Refresh all the table (A slide down animation is observed when we limit the refresh to the concerned section). // Note: The use of 'reloadData' handles the case where the account has been logged out. - [self refreshSettings]; + [self reloadData]; } failure:^(NSError *error) { // Display the data that has been loaded last time // Note: The use of 'reloadData' handles the case where the account has been logged out. - [self refreshSettings]; + [self reloadData]; }]; } -- (void)refreshSettings +- (void)reloadData { // Trigger a full table reloadData [self.tableView reloadData]; @@ -897,7 +897,7 @@ - (void)toggleBlacklistUnverifiedDevices:(id)sender - (void)dataSource:(MXKDataSource *)dataSource didCellChange:(id)changes { // Group data has been updated. Do a simple full reload - [self refreshSettings]; + [self reloadData]; } From 7daa6c917a02aa486d905fbb02fcc11158dfe415 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 30 Jan 2020 13:14:17 +0100 Subject: [PATCH 123/282] Settings > Security: Fix Giom's remarks --- Riot/Assets/en.lproj/Vector.strings | 4 +- Riot/Generated/Strings.swift | 16 ++--- .../ManageSessionViewController.h | 2 - .../ManageSessionViewController.m | 60 +---------------- .../Security/SecurityViewController.h | 3 - .../Security/SecurtiyViewController.m | 65 +++---------------- .../Modules/Settings/SettingsViewController.m | 11 ++++ 7 files changed, 34 insertions(+), 127 deletions(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 359af0327d..06a6177b2b 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -546,8 +546,8 @@ // Security settings "security_settings_title" = "Security"; -"security_settings_sessions" = "MY SESSIONS"; -"security_settings_sessions_description" = "Trust sessions to grant access to end-to-end encrypted messages. If you don’t recognise a session, change your login password and reset your Message Password used for Message Backup."; +"security_settings_crypto_sessions" = "MY SESSIONS"; +"security_settings_crypto_sessions_description" = "Trust sessions to grant access to end-to-end encrypted messages. If you don’t recognise a session, change your login password and reset your Message Password used for Message Backup."; "security_settings_backup" = "MESSAGE BACKUP"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 7a209d8354..388a956d7a 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -2646,17 +2646,17 @@ internal enum VectorL10n { internal static var securitySettingsBlacklistUnverifiedDevicesDescription: String { return VectorL10n.tr("Vector", "security_settings_blacklist_unverified_devices_description") } - /// Export keys manually - internal static var securitySettingsExportKeysManually: String { - return VectorL10n.tr("Vector", "security_settings_export_keys_manually") - } /// MY SESSIONS - internal static var securitySettingsSessions: String { - return VectorL10n.tr("Vector", "security_settings_sessions") + internal static var securitySettingsCryptoSessions: String { + return VectorL10n.tr("Vector", "security_settings_crypto_sessions") } /// Trust sessions to grant access to end-to-end encrypted messages. If you don’t recognise a session, change your login password and reset your Message Password used for Message Backup. - internal static var securitySettingsSessionsDescription: String { - return VectorL10n.tr("Vector", "security_settings_sessions_description") + internal static var securitySettingsCryptoSessionsDescription: String { + return VectorL10n.tr("Vector", "security_settings_crypto_sessions_description") + } + /// Export keys manually + internal static var securitySettingsExportKeysManually: String { + return VectorL10n.tr("Vector", "security_settings_export_keys_manually") } /// Security internal static var securitySettingsTitle: String { diff --git a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.h b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.h index 001ceefc05..c61242f7a5 100644 --- a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.h +++ b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.h @@ -16,8 +16,6 @@ #import -#import "DeviceView.h" - @interface ManageSessionViewController : MXKTableViewController diff --git a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m index de16eed1d8..be916dffba 100644 --- a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m +++ b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m @@ -47,21 +47,13 @@ }; -@interface ManageSessionViewController () < -MXKDataSourceDelegate, -MXKDeviceViewDelegate, -MXKEncryptionInfoViewDelegate> +@interface ManageSessionViewController () { // The device to display MXDevice *device; // Current alert (if any). UIAlertController *currentAlert; - - DeviceView *deviceView; - - // Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar. - id kAppDelegateDidTapStatusBarNotificationObserver; // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. id kThemeServiceDidChangeThemeNotificationObserver; @@ -134,10 +126,7 @@ - (void)userInterfaceThemeDidChange self.view.backgroundColor = self.tableView.backgroundColor; self.tableView.separatorColor = ThemeService.shared.theme.lineBreakColor; - if (self.tableView.dataSource) - { - [self reloadData]; - } + [self reloadData]; } - (UIStatusBarStyle)preferredStatusBarStyle @@ -163,41 +152,18 @@ - (void)destroy } } -- (void)onMatrixSessionStateDidChange:(NSNotification *)notif -{ - MXSession *mxSession = notif.object; - - // Check whether the concerned session is a new one which is not already associated with this view controller. - if (mxSession.state == MXSessionStateInitialised && [self.mxSessions indexOfObject:mxSession] != NSNotFound) - { - // Store this new session - [self addMatrixSession:mxSession]; - } - else - { - [super onMatrixSessionStateDidChange:notif]; - } -} - - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; // Screen tracking - [[Analytics sharedInstance] trackScreen:@"Settings"]; + [[Analytics sharedInstance] trackScreen:@"ManageSession"]; // Release the potential pushed view controller [self releasePushedViewController]; // Refresh display [self reloadData]; - - // Observe kAppDelegateDidTapStatusBarNotificationObserver. - kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - - [self.tableView setContentOffset:CGPointMake(-self.tableView.mxk_adjustedContentInset.left, -self.tableView.mxk_adjustedContentInset.top) animated:YES]; - - }]; } - (void)viewWillDisappear:(BOOL)animated @@ -209,12 +175,6 @@ - (void)viewWillDisappear:(BOOL)animated [currentAlert dismissViewControllerAnimated:NO completion:nil]; currentAlert = nil; } - - if (kAppDelegateDidTapStatusBarNotificationObserver) - { - [[NSNotificationCenter defaultCenter] removeObserver:kAppDelegateDidTapStatusBarNotificationObserver]; - kAppDelegateDidTapStatusBarNotificationObserver = nil; - } } #pragma mark - Internal methods @@ -258,12 +218,6 @@ - (void)reset { // Remove observers [[NSNotificationCenter defaultCenter] removeObserver:self]; - - if (deviceView) - { - [deviceView removeFromSuperview]; - deviceView = nil; - } } - (void)reloadData @@ -778,12 +732,4 @@ - (void)removeDevice }]; } -#pragma mark - MXKDataSourceDelegate - -- (void)dataSource:(MXKDataSource *)dataSource didCellChange:(id)changes -{ - // Group data has been updated. Do a simple full reload - [self reloadData]; -} - @end diff --git a/Riot/Modules/Settings/Security/SecurityViewController.h b/Riot/Modules/Settings/Security/SecurityViewController.h index 0ff4e9d971..1452f3144b 100644 --- a/Riot/Modules/Settings/Security/SecurityViewController.h +++ b/Riot/Modules/Settings/Security/SecurityViewController.h @@ -16,9 +16,6 @@ #import -#import "DeviceView.h" - - @interface SecurityViewController : MXKTableViewController + (SecurityViewController*)instantiateWithMatrixSession:(MXSession*)matrixSession; diff --git a/Riot/Modules/Settings/Security/SecurtiyViewController.m b/Riot/Modules/Settings/Security/SecurtiyViewController.m index 111fabae61..422562218a 100644 --- a/Riot/Modules/Settings/Security/SecurtiyViewController.m +++ b/Riot/Modules/Settings/Security/SecurtiyViewController.m @@ -32,7 +32,7 @@ enum { - SECTION_SESSIONS, + SECTION_CRYPTO_SESSIONS, SECTION_KEYBACKUP, SECTION_ADVANCED, SECTION_DEBUG, // TODO: To remove @@ -55,7 +55,6 @@ @interface SecurityViewController () < -MXKDataSourceDelegate, SettingsKeyBackupTableViewSectionDelegate, KeyBackupSetupCoordinatorBridgePresenterDelegate, KeyBackupRecoverCoordinatorBridgePresenterDelegate, @@ -67,9 +66,6 @@ @interface SecurityViewController () < // Devices NSMutableArray *devicesArray; - // Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar. - id kAppDelegateDidTapStatusBarNotificationObserver; - // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. id kThemeServiceDidChangeThemeNotificationObserver; @@ -163,10 +159,7 @@ - (void)userInterfaceThemeDidChange self.view.backgroundColor = self.tableView.backgroundColor; self.tableView.separatorColor = ThemeService.shared.theme.lineBreakColor; - if (self.tableView.dataSource) - { - [self reloadData]; - } + [self reloadData]; } - (UIStatusBarStyle)preferredStatusBarStyle @@ -202,28 +195,12 @@ - (void)destroy keyBackupRecoverCoordinatorBridgePresenter = nil; } -- (void)onMatrixSessionStateDidChange:(NSNotification *)notif -{ - MXSession *mxSession = notif.object; - - // Check whether the concerned session is a new one which is not already associated with this view controller. - if (mxSession.state == MXSessionStateInitialised && [self.mxSessions indexOfObject:mxSession] != NSNotFound) - { - // Store this new session - [self addMatrixSession:mxSession]; - } - else - { - [super onMatrixSessionStateDidChange:notif]; - } -} - - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; // Screen tracking - [[Analytics sharedInstance] trackScreen:@"Settings"]; + [[Analytics sharedInstance] trackScreen:@"Security"]; // Release the potential pushed view controller [self releasePushedViewController]; @@ -236,13 +213,6 @@ - (void)viewWillAppear:(BOOL)animated // Refresh devices in parallel [self loadDevices]; - - // Observe kAppDelegateDidTapStatusBarNotificationObserver. - kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - - [self.tableView setContentOffset:CGPointMake(-self.tableView.mxk_adjustedContentInset.left, -self.tableView.mxk_adjustedContentInset.top) animated:YES]; - - }]; } - (void)viewWillDisappear:(BOOL)animated @@ -254,12 +224,6 @@ - (void)viewWillDisappear:(BOOL)animated [currentAlert dismissViewControllerAnimated:NO completion:nil]; currentAlert = nil; } - - if (kAppDelegateDidTapStatusBarNotificationObserver) - { - [[NSNotificationCenter defaultCenter] removeObserver:kAppDelegateDidTapStatusBarNotificationObserver]; - kAppDelegateDidTapStatusBarNotificationObserver = nil; - } } #pragma mark - Internal methods @@ -472,7 +436,7 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger switch (section) { - case SECTION_SESSIONS: + case SECTION_CRYPTO_SESSIONS: count = devicesArray.count + 1; break; case SECTION_KEYBACKUP: @@ -597,7 +561,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N cell.backgroundColor = [UIColor redColor]; MXSession* session = self.mainSession; - if (section == SECTION_SESSIONS) + if (section == SECTION_CRYPTO_SESSIONS) { if (row < devicesArray.count) { @@ -606,7 +570,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N else if (row == devicesArray.count) { cell = [self descriptionCellForTableView:tableView - withText:NSLocalizedStringFromTable(@"security_settings_sessions_description", @"Vector", nil) ]; + withText:NSLocalizedStringFromTable(@"security_settings_crypto_sessions_description", @"Vector", nil) ]; } } @@ -718,8 +682,8 @@ - (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSectio { switch (section) { - case SECTION_SESSIONS: - return NSLocalizedStringFromTable(@"security_settings_sessions", @"Vector", nil); + case SECTION_CRYPTO_SESSIONS: + return NSLocalizedStringFromTable(@"security_settings_crypto_sessions", @"Vector", nil); case SECTION_KEYBACKUP: return NSLocalizedStringFromTable(@"security_settings_backup", @"Vector", nil); case SECTION_ADVANCED: @@ -773,7 +737,7 @@ - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)ce - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { - if (section == SECTION_SESSIONS) + if (section == SECTION_CRYPTO_SESSIONS) { return 44; } @@ -792,7 +756,7 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath NSInteger section = indexPath.section; NSInteger row = indexPath.row; - if (section == SECTION_SESSIONS) + if (section == SECTION_CRYPTO_SESSIONS) { NSUInteger deviceIndex = row; if (deviceIndex < devicesArray.count) @@ -892,15 +856,6 @@ - (void)toggleBlacklistUnverifiedDevices:(id)sender } -#pragma mark - MXKDataSourceDelegate - -- (void)dataSource:(MXKDataSource *)dataSource didCellChange:(id)changes -{ - // Group data has been updated. Do a simple full reload - [self reloadData]; -} - - #pragma mark - SettingsKeyBackupTableViewSectionDelegate - (void)settingsKeyBackupTableViewSectionDidUpdate:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index 1fbb18f386..a65477d660 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -2661,6 +2661,17 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath } } } + else if (section == SETTINGS_SECTION_CONTACTS_INDEX) + { + if (row == localContactsPhoneBookCountryIndex) + { + CountryPickerViewController *countryPicker = [CountryPickerViewController countryPickerViewController]; + countryPicker.view.tag = SETTINGS_SECTION_CONTACTS_INDEX; + countryPicker.delegate = self; + countryPicker.showCountryCallingCode = YES; + [self pushViewController:countryPicker]; + } + } else if (section == SETTINGS_SECTION_SECURITY_INDEX) { switch (row) From 3219416f338de49c918df1377ea20348e40eea0b Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 30 Jan 2020 13:24:13 +0100 Subject: [PATCH 124/282] Settings > Security: Fix file name --- Riot.xcodeproj/project.pbxproj | 8 ++++---- ...{SecurtiyViewController.m => SecurityViewController.m} | 0 2 files changed, 4 insertions(+), 4 deletions(-) rename Riot/Modules/Settings/Security/{SecurtiyViewController.m => SecurityViewController.m} (100%) diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index d002fba7c4..efad11f477 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -79,7 +79,7 @@ 32891D75226728EE00C82226 /* DeviceVerificationDataLoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32891D73226728EE00C82226 /* DeviceVerificationDataLoadingViewController.swift */; }; 32891D76226728EF00C82226 /* DeviceVerificationDataLoadingViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 32891D74226728EE00C82226 /* DeviceVerificationDataLoadingViewController.storyboard */; }; 3291DC8A23E0BE820009732F /* Security.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3291DC8923E0BE820009732F /* Security.storyboard */; }; - 3291DC8D23E0BFF10009732F /* SecurtiyViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3291DC8C23E0BFF10009732F /* SecurtiyViewController.m */; }; + 3291DC8D23E0BFF10009732F /* SecurityViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3291DC8C23E0BFF10009732F /* SecurityViewController.m */; }; 329E746622CD02EA006F9797 /* BubbleReactionActionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 329E746422CD02EA006F9797 /* BubbleReactionActionViewCell.xib */; }; 329E746722CD02EA006F9797 /* BubbleReactionActionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 329E746522CD02EA006F9797 /* BubbleReactionActionViewCell.swift */; }; 32A6001622C661100042C1D9 /* EditHistoryViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32A6000D22C661100042C1D9 /* EditHistoryViewState.swift */; }; @@ -752,7 +752,7 @@ 32891D74226728EE00C82226 /* DeviceVerificationDataLoadingViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = DeviceVerificationDataLoadingViewController.storyboard; sourceTree = ""; }; 3291DC8923E0BE820009732F /* Security.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Security.storyboard; sourceTree = ""; }; 3291DC8B23E0BFF10009732F /* SecurityViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SecurityViewController.h; sourceTree = ""; }; - 3291DC8C23E0BFF10009732F /* SecurtiyViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SecurtiyViewController.m; sourceTree = ""; }; + 3291DC8C23E0BFF10009732F /* SecurityViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SecurityViewController.m; sourceTree = ""; }; 329E746422CD02EA006F9797 /* BubbleReactionActionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BubbleReactionActionViewCell.xib; sourceTree = ""; }; 329E746522CD02EA006F9797 /* BubbleReactionActionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BubbleReactionActionViewCell.swift; sourceTree = ""; }; 32A6000D22C661100042C1D9 /* EditHistoryViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditHistoryViewState.swift; sourceTree = ""; }; @@ -1818,7 +1818,7 @@ children = ( 32D5D15C23E1EE2700E3E37C /* ManageSession */, 3291DC8B23E0BFF10009732F /* SecurityViewController.h */, - 3291DC8C23E0BFF10009732F /* SecurtiyViewController.m */, + 3291DC8C23E0BFF10009732F /* SecurityViewController.m */, 3291DC8923E0BE820009732F /* Security.storyboard */, ); path = Security; @@ -4612,7 +4612,7 @@ B1B558D420EF768F00210D55 /* RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.m in Sources */, B169331420F3CAFC00746532 /* PublicRoomTableViewCell.m in Sources */, 32BF995721FB07A400698084 /* SettingsKeyBackupTableViewSection.swift in Sources */, - 3291DC8D23E0BFF10009732F /* SecurtiyViewController.m in Sources */, + 3291DC8D23E0BFF10009732F /* SecurityViewController.m in Sources */, B1C45A88232A8C2600165425 /* SettingsIdentityServerViewState.swift in Sources */, B14F142F22144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewModelType.swift in Sources */, 32DB557822FDADE50016329E /* ServiceTermsModalScreenViewState.swift in Sources */, diff --git a/Riot/Modules/Settings/Security/SecurtiyViewController.m b/Riot/Modules/Settings/Security/SecurityViewController.m similarity index 100% rename from Riot/Modules/Settings/Security/SecurtiyViewController.m rename to Riot/Modules/Settings/Security/SecurityViewController.m From 264c0e2518d04a71f0e0b85fd70f5ce73ed0ada7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Wed, 29 Jan 2020 12:47:43 +0000 Subject: [PATCH 125/282] Translated using Weblate (French) Currently translated at 100.0% (862 of 862 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/fr/ --- Riot/Assets/fr.lproj/Vector.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index c85ff08531..41cfb59eba 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -957,3 +957,4 @@ "key_verification_tile_conclusion_done_title" = "Vérifié"; "key_verification_tile_conclusion_warning_title" = "Connexion non approuvée"; "key_verification_incoming_request_incoming_alert_message" = "%@ veut vérifier"; +"settings_labs_enable_cross_signing" = "Activer la vérification croisée pour vérifier par utilisateurs plutôt que par appareil (en développement)"; From b4dc2b6480b0002b258446d366d3b1e167be001d Mon Sep 17 00:00:00 2001 From: random Date: Wed, 29 Jan 2020 11:51:04 +0000 Subject: [PATCH 126/282] Translated using Weblate (Italian) Currently translated at 100.0% (862 of 862 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/it/ --- Riot/Assets/it.lproj/Vector.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Assets/it.lproj/Vector.strings b/Riot/Assets/it.lproj/Vector.strings index 2cbb8ad83e..39d9f1af1a 100644 --- a/Riot/Assets/it.lproj/Vector.strings +++ b/Riot/Assets/it.lproj/Vector.strings @@ -929,3 +929,4 @@ "key_verification_tile_conclusion_done_title" = "Verificato"; "key_verification_tile_conclusion_warning_title" = "Accesso non fidato"; "key_verification_incoming_request_incoming_alert_message" = "%@ vuole verificare"; +"settings_labs_enable_cross_signing" = "Attiva la firma incrociata per la verifica per-utente invece di per-dispositivo (in sviluppo)"; From 750be94854de9a27cdc1c210019b2d131b3025af Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 30 Jan 2020 16:08:58 +0100 Subject: [PATCH 127/282] E2E by default: canEnableE2EByDefaultInNewRoomWithUsers is now defined in the SDK --- Riot/Categories/MXSession+Riot.h | 14 -------------- Riot/Categories/MXSession+Riot.m | 33 -------------------------------- 2 files changed, 47 deletions(-) diff --git a/Riot/Categories/MXSession+Riot.h b/Riot/Categories/MXSession+Riot.h index b907e64a18..9bc296a980 100644 --- a/Riot/Categories/MXSession+Riot.h +++ b/Riot/Categories/MXSession+Riot.h @@ -25,18 +25,4 @@ */ - (NSUInteger)riot_missedDiscussionsCount; -/** - Decide if E2E must be enabled in a new room with a list users - - @param userIds the list of users; - - @param success A block object called when the operation succeeds. - @param failure A block object called when the operation fails. - - @return a MXHTTPOperation instance. - */ -- (MXHTTPOperation*)canEnableE2EByDefaultInNewRoomWithUsers:(NSArray*)userIds - success:(void (^)(BOOL canEnableE2E))success - failure:(void (^)(NSError *error))failure; - @end diff --git a/Riot/Categories/MXSession+Riot.m b/Riot/Categories/MXSession+Riot.m index 1281f4db41..0f2f9782d8 100644 --- a/Riot/Categories/MXSession+Riot.m +++ b/Riot/Categories/MXSession+Riot.m @@ -49,37 +49,4 @@ - (NSUInteger)riot_missedDiscussionsCount return missedDiscussionsCount; } -- (MXHTTPOperation*)canEnableE2EByDefaultInNewRoomWithUsers:(NSArray*)userIds - success:(void (^)(BOOL canEnableE2E))success - failure:(void (^)(NSError *error))failure -{ - MXHTTPOperation *operation; - if (RiotSettings.shared.enableCrossSigning) - { - // Check whether all users have uploaded device keys before. - // If so, encryption can be enabled in the new room - operation = [self.crypto downloadKeys:userIds forceDownload:NO success:^(MXUsersDevicesMap *usersDevicesInfoMap, NSDictionary *crossSigningKeysMap) { - - BOOL allUsersHaveDeviceKeys = YES; - for (NSString *userId in userIds) - { - if ([usersDevicesInfoMap deviceIdsForUser:userId].count == 0) - { - allUsersHaveDeviceKeys = NO; - break; - } - } - - success(allUsersHaveDeviceKeys); - - } failure:failure]; - } - else - { - success(NO); - } - - return operation; -} - @end From 82c1578aaaa5b54b1ee0d152351a6bca018734bc Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 30 Jan 2020 17:07:53 +0100 Subject: [PATCH 128/282] Room Shield: Use sync version of [MXRoom membersTrustLevelSummary:] https://github.com/vector-im/riot-ios/issues/2956 to break keys downloads in loop --- Riot/Modules/Room/DataSources/RoomDataSource.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m index 3bd0d0165f..93b77ad81c 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -205,7 +205,7 @@ - (void)deviceInfoTrustLevelDidChange:(NSNotification*)notification if (userId) { - [self encryptionTrustLevelDidChangeRelatedToUserId:userId]; + [self encryptionTrustLevelDidChangeRelatedToUserId:userId forceDownload:NO]; } } @@ -217,16 +217,16 @@ - (void)crossSigningInfoTrustLevelDidChange:(NSNotification*)notification if (userId) { - [self encryptionTrustLevelDidChangeRelatedToUserId:userId]; + [self encryptionTrustLevelDidChangeRelatedToUserId:userId forceDownload:NO]; } } - (void)fetchEncryptionTrustedLevel { - [self encryptionTrustLevelDidChangeRelatedToUserId:self.mxSession.myUser.userId]; + [self encryptionTrustLevelDidChangeRelatedToUserId:self.mxSession.myUser.userId forceDownload:YES]; } -- (void)encryptionTrustLevelDidChangeRelatedToUserId:(NSString*)userId +- (void)encryptionTrustLevelDidChangeRelatedToUserId:(NSString*)userId forceDownload:(BOOL)forceDownload { if (!self.room.summary.isEncrypted) { @@ -239,7 +239,7 @@ - (void)encryptionTrustLevelDidChangeRelatedToUserId:(NSString*)userId // If user belongs to the room refresh the trust level if (roomMember) { - [self.room membersTrustLevelSummaryWithSuccess:^(MXUsersTrustLevelSummary *usersTrustLevelSummary) { + [self.room membersTrustLevelSummaryWithForceDownload:forceDownload success:^(MXUsersTrustLevelSummary *usersTrustLevelSummary) { RoomEncryptionTrustLevel roomEncryptionTrustLevel; From 6819d3d00529643c533c5df8c11e6e557a1cc19a Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 30 Jan 2020 17:38:33 +0100 Subject: [PATCH 129/282] User verification: Implement start user verification screen. --- .../UserVerificationStartCoordinator.swift | 77 ++++++ ...UserVerificationStartCoordinatorType.swift | 32 +++ .../UserVerificationStartViewAction.swift | 26 ++ ...VerificationStartViewController.storyboard | 101 ++++++++ .../UserVerificationStartViewController.swift | 229 ++++++++++++++++++ .../UserVerificationStartViewModel.swift | 200 +++++++++++++++ .../UserVerificationStartViewModelType.swift | 41 ++++ .../UserVerificationStartViewState.swift | 29 +++ 8 files changed, 735 insertions(+) create mode 100644 Riot/Modules/UserVerification/Start/UserVerificationStartCoordinator.swift create mode 100644 Riot/Modules/UserVerification/Start/UserVerificationStartCoordinatorType.swift create mode 100644 Riot/Modules/UserVerification/Start/UserVerificationStartViewAction.swift create mode 100644 Riot/Modules/UserVerification/Start/UserVerificationStartViewController.storyboard create mode 100644 Riot/Modules/UserVerification/Start/UserVerificationStartViewController.swift create mode 100644 Riot/Modules/UserVerification/Start/UserVerificationStartViewModel.swift create mode 100644 Riot/Modules/UserVerification/Start/UserVerificationStartViewModelType.swift create mode 100644 Riot/Modules/UserVerification/Start/UserVerificationStartViewState.swift diff --git a/Riot/Modules/UserVerification/Start/UserVerificationStartCoordinator.swift b/Riot/Modules/UserVerification/Start/UserVerificationStartCoordinator.swift new file mode 100644 index 0000000000..76910a825a --- /dev/null +++ b/Riot/Modules/UserVerification/Start/UserVerificationStartCoordinator.swift @@ -0,0 +1,77 @@ +// File created from ScreenTemplate +// $ createScreen.sh Start UserVerificationStart +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation +import UIKit + +final class UserVerificationStartCoordinator: UserVerificationStartCoordinatorType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let roomMember: MXRoomMember + + private var userVerificationStartViewModel: UserVerificationStartViewModelType + private let userVerificationStartViewController: UserVerificationStartViewController + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: UserVerificationStartCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, roomMember: MXRoomMember) { + self.session = session + self.roomMember = roomMember + + let userVerificationStartViewModel = UserVerificationStartViewModel(session: self.session, roomMember: self.roomMember) + let userVerificationStartViewController = UserVerificationStartViewController.instantiate(with: userVerificationStartViewModel) + self.userVerificationStartViewModel = userVerificationStartViewModel + self.userVerificationStartViewController = userVerificationStartViewController + } + + // MARK: - Public methods + + func start() { + self.userVerificationStartViewModel.coordinatorDelegate = self + } + + func toPresentable() -> UIViewController { + return self.userVerificationStartViewController + } +} + +// MARK: - UserVerificationStartViewModelCoordinatorDelegate +extension UserVerificationStartCoordinator: UserVerificationStartViewModelCoordinatorDelegate { + func userVerificationStartViewModelDidCancel(_ viewModel: UserVerificationStartViewModelType) { + self.delegate?.userVerificationStartCoordinatorDidCancel(self) + } + + func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, didCompleteWithOutgoingTransaction transaction: MXSASTransaction) { + self.delegate?.userVerificationStartCoordinator(self, didCompleteWithOutgoingTransaction: transaction) + } + + func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, didTransactionCancelled transaction: MXSASTransaction) { + self.delegate?.userVerificationStartCoordinator(self, didTransactionCancelled: transaction) + } +} diff --git a/Riot/Modules/UserVerification/Start/UserVerificationStartCoordinatorType.swift b/Riot/Modules/UserVerification/Start/UserVerificationStartCoordinatorType.swift new file mode 100644 index 0000000000..2f59b19732 --- /dev/null +++ b/Riot/Modules/UserVerification/Start/UserVerificationStartCoordinatorType.swift @@ -0,0 +1,32 @@ +// File created from ScreenTemplate +// $ createScreen.sh Start UserVerificationStart +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol UserVerificationStartCoordinatorDelegate: class { + func userVerificationStartCoordinator(_ coordinator: UserVerificationStartCoordinatorType, didCompleteWithOutgoingTransaction transaction: MXSASTransaction) + + func userVerificationStartCoordinator(_ coordinator: UserVerificationStartCoordinatorType, didTransactionCancelled transaction: MXSASTransaction) + + func userVerificationStartCoordinatorDidCancel(_ coordinator: UserVerificationStartCoordinatorType) +} + +/// `UserVerificationStartCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow. +protocol UserVerificationStartCoordinatorType: Coordinator, Presentable { + var delegate: UserVerificationStartCoordinatorDelegate? { get } +} diff --git a/Riot/Modules/UserVerification/Start/UserVerificationStartViewAction.swift b/Riot/Modules/UserVerification/Start/UserVerificationStartViewAction.swift new file mode 100644 index 0000000000..3d0e507620 --- /dev/null +++ b/Riot/Modules/UserVerification/Start/UserVerificationStartViewAction.swift @@ -0,0 +1,26 @@ +// File created from ScreenTemplate +// $ createScreen.sh Start UserVerificationStart +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// UserVerificationStartViewController view actions exposed to view model +enum UserVerificationStartViewAction { + case loadData + case startVerification + case cancel +} diff --git a/Riot/Modules/UserVerification/Start/UserVerificationStartViewController.storyboard b/Riot/Modules/UserVerification/Start/UserVerificationStartViewController.storyboard new file mode 100644 index 0000000000..5af56d06b5 --- /dev/null +++ b/Riot/Modules/UserVerification/Start/UserVerificationStartViewController.storyboard @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/UserVerification/Start/UserVerificationStartViewController.swift b/Riot/Modules/UserVerification/Start/UserVerificationStartViewController.swift new file mode 100644 index 0000000000..b57301d750 --- /dev/null +++ b/Riot/Modules/UserVerification/Start/UserVerificationStartViewController.swift @@ -0,0 +1,229 @@ +// File created from ScreenTemplate +// $ createScreen.sh Start UserVerificationStart +/* + Copyright 2020 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 UserVerificationStartViewController: UIViewController { + + // MARK: - Constants + + private enum Constants { + static let verifyButtonCornerRadius: CGFloat = 8.0 + static let informationTextDefaultFont = UIFont.systemFont(ofSize: 15.0) + static let informationTextBoldFont = UIFont.systemFont(ofSize: 15.0, weight: .medium) + } + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var informationLabel: UILabel! + + @IBOutlet private weak var startVerificationButton: UIButton! + @IBOutlet private weak var verificationWaitingLabel: UILabel! + + @IBOutlet private weak var additionalInformationLabel: UILabel! + + // MARK: Private + + private var viewModel: UserVerificationStartViewModelType! + private var theme: Theme! + private var errorPresenter: MXKErrorPresentation! + private var activityPresenter: ActivityIndicatorPresenter! + + // MARK: - Setup + + class func instantiate(with viewModel: UserVerificationStartViewModelType) -> UserVerificationStartViewController { + let viewController = StoryboardScene.UserVerificationStartViewController.initialScene.instantiate() + viewController.viewModel = viewModel + viewController.theme = ThemeService.shared().theme + return viewController + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + self.title = "Verify user" + + self.setupViews() + + self.activityPresenter = ActivityIndicatorPresenter() + self.errorPresenter = MXKErrorAlertPresentation() + + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + + self.viewModel.viewDelegate = self + + self.viewModel.process(viewAction: .loadData) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.theme.statusBarStyle + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + self.startVerificationButton.layer.cornerRadius = Constants.verifyButtonCornerRadius + } + + // MARK: - Private + + private func update(theme: Theme) { + self.theme = theme + + self.view.backgroundColor = theme.headerBackgroundColor + + if let navigationBar = self.navigationController?.navigationBar { + theme.applyStyle(onNavigationBar: navigationBar) + } + + self.informationLabel.textColor = theme.textPrimaryColor + self.startVerificationButton.vc_setBackgroundColor(theme.tintColor, for: .normal) + self.verificationWaitingLabel.textColor = theme.textSecondaryColor + self.additionalInformationLabel.textColor = theme.textSecondaryColor + } + + 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() { + let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in + self?.cancelButtonAction() + } + + self.navigationItem.rightBarButtonItem = cancelBarButtonItem + + self.startVerificationButton.layer.masksToBounds = true + self.startVerificationButton.setTitle("Start verification", for: .normal) + } + + private func render(viewState: UserVerificationStartViewState) { + switch viewState { + case .loading: + self.renderLoading() + case .loaded(let viewData): + self.renderLoaded(viewData: viewData) + case .error(let error): + self.render(error: error) + case .verificationPending: + self.renderVerificationPending() + case .cancelled(let reason): + self.renderCancelled(reason: reason) + case .cancelledByMe(let reason): + self.renderCancelledByMe(reason: reason) + } + } + + private func renderLoading() { + self.activityPresenter.presentActivityIndicator(on: self.view, animated: true) + } + + private func renderLoaded(viewData: UserVerificationStartViewData) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + + self.informationLabel.attributedText = self.buildInformationAttributedText(with: viewData.userId) + self.verificationWaitingLabel.text = self.buildVerificationWaitingText(with: viewData) + } + + private func render(error: Error) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil) + } + + private func renderVerificationPending() { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + self.startVerificationButton.isHidden = true + self.verificationWaitingLabel.isHidden = false + } + + private func renderCancelled(reason: MXTransactionCancelCode) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + + self.errorPresenter.presentError(from: self, title: "", message: VectorL10n.deviceVerificationCancelled, animated: true) { + self.viewModel.process(viewAction: .cancel) + } + } + + private func renderCancelledByMe(reason: MXTransactionCancelCode) { + if reason.value != MXTransactionCancelCode.user().value { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + + self.errorPresenter.presentError(from: self, title: "", message: VectorL10n.deviceVerificationCancelledByMe(reason.humanReadable), animated: true) { + self.viewModel.process(viewAction: .cancel) + } + } else { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + } + } + + private func buildInformationAttributedText(with userId: String) -> NSAttributedString { + + let informationAttributedText: NSMutableAttributedString = NSMutableAttributedString() + + let informationTextDefaultAttributes: [NSAttributedString.Key: Any] = [.foregroundColor: self.theme.textPrimaryColor, + .font: Constants.informationTextDefaultFont] + + let informationTextBoldAttributes: [NSAttributedString.Key: Any] = [.foregroundColor: self.theme.textPrimaryColor, + .font: Constants.informationTextBoldFont] + + let informationAttributedStringPart1 = NSAttributedString(string: "For extra security, verify ", attributes: informationTextDefaultAttributes) + let informationAttributedStringPart2 = NSAttributedString(string: userId, attributes: informationTextBoldAttributes) + let informationAttributedStringPart3 = NSAttributedString(string: " by checking a one-time code on both your devices.", attributes: informationTextDefaultAttributes) + + informationAttributedText.append(informationAttributedStringPart1) + informationAttributedText.append(informationAttributedStringPart2) + informationAttributedText.append(informationAttributedStringPart3) + + return informationAttributedText + } + + private func buildVerificationWaitingText(with viewData: UserVerificationStartViewData) -> String { + let userName = viewData.userDisplayName ?? viewData.userId + return "Waiting for \(userName)…" + } + + // MARK: - Actions + + @IBAction private func startVerificationButtonAction(_ sender: Any) { + self.viewModel.process(viewAction: .startVerification) + } + + private func cancelButtonAction() { + self.viewModel.process(viewAction: .cancel) + } +} + + +// MARK: - UserVerificationStartViewModelViewDelegate +extension UserVerificationStartViewController: UserVerificationStartViewModelViewDelegate { + + func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, didUpdateViewState viewSate: UserVerificationStartViewState) { + self.render(viewState: viewSate) + } +} diff --git a/Riot/Modules/UserVerification/Start/UserVerificationStartViewModel.swift b/Riot/Modules/UserVerification/Start/UserVerificationStartViewModel.swift new file mode 100644 index 0000000000..4ef9b3e0d5 --- /dev/null +++ b/Riot/Modules/UserVerification/Start/UserVerificationStartViewModel.swift @@ -0,0 +1,200 @@ +// File created from ScreenTemplate +// $ createScreen.sh Start UserVerificationStart +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +enum UserVerificationStartViewModelError: Error { + case keyVerificationRequestExpired +} + +struct UserVerificationStartViewData { + let userId: String + let userDisplayName: String? + let userAvatarURL: String? +} + +final class UserVerificationStartViewModel: UserVerificationStartViewModelType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let roomMember: MXRoomMember + private let verificationManager: MXDeviceVerificationManager + + private var keyVerificationRequest: MXKeyVerificationRequest? + + private var viewData: UserVerificationStartViewData { + return UserVerificationStartViewData(userId: self.roomMember.userId, userDisplayName: self.roomMember.displayname, userAvatarURL: self.roomMember.avatarUrl) + } + + // MARK: Public + + weak var viewDelegate: UserVerificationStartViewModelViewDelegate? + weak var coordinatorDelegate: UserVerificationStartViewModelCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, roomMember: MXRoomMember) { + self.session = session + self.verificationManager = session.crypto.deviceVerificationManager + self.roomMember = roomMember + } + + deinit { + } + + // MARK: - Public + + func process(viewAction: UserVerificationStartViewAction) { + switch viewAction { + case .loadData: + self.loadData() + case .startVerification: + self.startVerification() + case .cancel: + self.cancelKeyVerificationRequest() + self.coordinatorDelegate?.userVerificationStartViewModelDidCancel(self) + } + } + + // MARK: - Private + + private func loadData() { + self.update(viewState: .loaded(self.viewData)) + } + + private func startVerification() { + self.update(viewState: .verificationPending) + + self.verificationManager.requestVerificationByDM(withUserId: self.roomMember.userId, + roomId: nil, + fallbackText: "", + methods: [MXKeyVerificationMethodSAS], + success: { [weak self] (keyVerificationRequest) in + guard let self = self else { + return + } + + self.keyVerificationRequest = keyVerificationRequest + self.update(viewState: .loaded(self.viewData)) + self.registerKeyVerificationDidChangeNotification(keyVerificationRequest: keyVerificationRequest) + self.registerTransactionDidStateChangeNotification() + + }, failure: { [weak self] error in + self?.update(viewState: .error(error)) + }) + } + + private func update(viewState: UserVerificationStartViewState) { + self.viewDelegate?.userVerificationStartViewModel(self, didUpdateViewState: viewState) + } + + private func cancelKeyVerificationRequest() { + guard let keyVerificationRequest = self.keyVerificationRequest else { + return + } + + keyVerificationRequest.cancel(with: MXTransactionCancelCode.user(), success: nil, failure: nil) + } + + // MARK: - MXDeviceVerificationTransactionDidChange + + private func registerTransactionDidStateChangeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: .MXDeviceVerificationTransactionDidChange, object: nil) + } + + private func unregisterTransactionDidStateChangeNotification() { + NotificationCenter.default.removeObserver(self, name: .MXDeviceVerificationTransactionDidChange, object: nil) + } + + @objc private func transactionDidStateChange(notification: Notification) { + guard let transaction = notification.object as? MXIncomingSASTransaction else { + return + } + + guard let keyVerificationRequest = self.keyVerificationRequest, + let transactionDMEventId = transaction.dmEventId, + keyVerificationRequest.requestId == transactionDMEventId else { + return + } + + switch transaction.state { + case MXSASTransactionStateShowSAS: + self.unregisterTransactionDidStateChangeNotification() + self.coordinatorDelegate?.userVerificationStartViewModel(self, didCompleteWithOutgoingTransaction: transaction) + case MXSASTransactionStateCancelled: + guard let reason = transaction.reasonCancelCode else { + return + } + self.unregisterTransactionDidStateChangeNotification() + self.update(viewState: .cancelled(reason)) + case MXSASTransactionStateCancelledByMe: + guard let reason = transaction.reasonCancelCode else { + return + } + self.unregisterTransactionDidStateChangeNotification() + self.update(viewState: .cancelledByMe(reason)) + default: + break + } + } + + // MARK: - MXDeviceVerificationTransactionDidChange + + private func registerKeyVerificationDidChangeNotification(keyVerificationRequest: MXKeyVerificationRequest) { + NotificationCenter.default.addObserver(self, selector: #selector(keyVerificationRequestDidChange(notification:)), name: .MXKeyVerificationRequestDidChange, object: keyVerificationRequest) + } + + private func unregisterKeyVerificationDidChangeNotification() { + NotificationCenter.default.removeObserver(self, name: .MXKeyVerificationRequestDidChange, object: nil) + } + + @objc private func keyVerificationRequestDidChange(notification: Notification) { + guard let keyVerificationRequest = notification.object as? MXKeyVerificationByDMRequest else { + return + } + + guard let currentKeyVerificationRequest = self.keyVerificationRequest, keyVerificationRequest.requestId == currentKeyVerificationRequest.requestId else { + return + } + + switch keyVerificationRequest.state { + case MXKeyVerificationRequestStateAccepted: + self.unregisterKeyVerificationDidChangeNotification() + case MXKeyVerificationRequestStateCancelled: + guard let reason = keyVerificationRequest.reasonCancelCode else { + return + } + self.unregisterKeyVerificationDidChangeNotification() + self.update(viewState: .cancelled(reason)) + case MXKeyVerificationRequestStateCancelledByMe: + guard let reason = keyVerificationRequest.reasonCancelCode else { + return + } + self.unregisterKeyVerificationDidChangeNotification() + self.update(viewState: .cancelledByMe(reason)) + case MXKeyVerificationRequestStateExpired: + self.unregisterKeyVerificationDidChangeNotification() + self.update(viewState: .error(UserVerificationStartViewModelError.keyVerificationRequestExpired)) + default: + break + } + } +} diff --git a/Riot/Modules/UserVerification/Start/UserVerificationStartViewModelType.swift b/Riot/Modules/UserVerification/Start/UserVerificationStartViewModelType.swift new file mode 100644 index 0000000000..f50a4ccb8d --- /dev/null +++ b/Riot/Modules/UserVerification/Start/UserVerificationStartViewModelType.swift @@ -0,0 +1,41 @@ +// File created from ScreenTemplate +// $ createScreen.sh Start UserVerificationStart +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol UserVerificationStartViewModelViewDelegate: class { + func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, didUpdateViewState viewSate: UserVerificationStartViewState) +} + +protocol UserVerificationStartViewModelCoordinatorDelegate: class { + + func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, didCompleteWithOutgoingTransaction transaction: MXSASTransaction) + + func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, didTransactionCancelled transaction: MXSASTransaction) + + func userVerificationStartViewModelDidCancel(_ viewModel: UserVerificationStartViewModelType) +} + +/// Protocol describing the view model used by `UserVerificationStartViewController` +protocol UserVerificationStartViewModelType { + + var viewDelegate: UserVerificationStartViewModelViewDelegate? { get set } + var coordinatorDelegate: UserVerificationStartViewModelCoordinatorDelegate? { get set } + + func process(viewAction: UserVerificationStartViewAction) +} diff --git a/Riot/Modules/UserVerification/Start/UserVerificationStartViewState.swift b/Riot/Modules/UserVerification/Start/UserVerificationStartViewState.swift new file mode 100644 index 0000000000..efbb54c565 --- /dev/null +++ b/Riot/Modules/UserVerification/Start/UserVerificationStartViewState.swift @@ -0,0 +1,29 @@ +// File created from ScreenTemplate +// $ createScreen.sh Start UserVerificationStart +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// UserVerificationStartViewController view state +enum UserVerificationStartViewState { + case loading + case loaded(UserVerificationStartViewData) + case verificationPending + case cancelled(MXTransactionCancelCode) + case cancelledByMe(MXTransactionCancelCode) + case error(Error) +} From 46efdd164ad1a73720dcce20cd0466f9dc6d1a45 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 30 Jan 2020 17:41:51 +0100 Subject: [PATCH 130/282] User verification: Implement single session status screen. --- ...VerificationSessionStatusCoordinator.swift | 75 +++++ ...ficationSessionStatusCoordinatorType.swift | 29 ++ ...rVerificationSessionStatusViewAction.swift | 26 ++ ...tionSessionStatusViewController.storyboard | 209 ++++++++++++++ ...ificationSessionStatusViewController.swift | 267 ++++++++++++++++++ ...erVerificationSessionStatusViewModel.swift | 95 +++++++ ...rificationSessionStatusViewModelType.swift | 37 +++ ...erVerificationSessionStatusViewState.swift | 26 ++ 8 files changed, 764 insertions(+) create mode 100644 Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusCoordinator.swift create mode 100644 Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusCoordinatorType.swift create mode 100644 Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewAction.swift create mode 100644 Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewController.storyboard create mode 100644 Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewController.swift create mode 100644 Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewModel.swift create mode 100644 Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewModelType.swift create mode 100644 Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewState.swift diff --git a/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusCoordinator.swift b/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusCoordinator.swift new file mode 100644 index 0000000000..3a35dab77f --- /dev/null +++ b/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusCoordinator.swift @@ -0,0 +1,75 @@ +// File created from ScreenTemplate +// $ createScreen.sh SessionStatus UserVerificationSessionStatus +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation +import UIKit + +final class UserVerificationSessionStatusCoordinator: UserVerificationSessionStatusCoordinatorType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let userId: String + private let deviceId: String + private var userVerificationSessionStatusViewModel: UserVerificationSessionStatusViewModelType + private let userVerificationSessionStatusViewController: UserVerificationSessionStatusViewController + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: UserVerificationSessionStatusCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, userId: String, userDisplayName: String?, deviceId: String) { + self.session = session + self.userId = userId + self.deviceId = deviceId + + let userVerificationSessionStatusViewModel = UserVerificationSessionStatusViewModel(session: self.session, userId: userId, userDisplayName: userDisplayName, deviceId: deviceId) + let userVerificationSessionStatusViewController = UserVerificationSessionStatusViewController.instantiate(with: userVerificationSessionStatusViewModel) + self.userVerificationSessionStatusViewModel = userVerificationSessionStatusViewModel + self.userVerificationSessionStatusViewController = userVerificationSessionStatusViewController + } + + // MARK: - Public methods + + func start() { + self.userVerificationSessionStatusViewModel.coordinatorDelegate = self + } + + func toPresentable() -> UIViewController { + return self.userVerificationSessionStatusViewController + } +} + +// MARK: - UserVerificationSessionStatusViewModelCoordinatorDelegate +extension UserVerificationSessionStatusCoordinator: UserVerificationSessionStatusViewModelCoordinatorDelegate { + + func userVerificationSessionStatusViewModel(_ viewModel: UserVerificationSessionStatusViewModelType, wantsToManuallyVerifyDeviceWithId deviceId: String, for userId: String) { + self.delegate?.userVerificationSessionStatusCoordinator(self, wantsToManuallyVerifyDeviceWithId: deviceId, for: userId) + } + + func userVerificationSessionStatusViewModelDidClose(_ viewModel: UserVerificationSessionStatusViewModelType) { + self.delegate?.userVerificationSessionStatusCoordinatorDidClose(self) + } +} diff --git a/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusCoordinatorType.swift b/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusCoordinatorType.swift new file mode 100644 index 0000000000..2808bd804b --- /dev/null +++ b/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusCoordinatorType.swift @@ -0,0 +1,29 @@ +// File created from ScreenTemplate +// $ createScreen.sh SessionStatus UserVerificationSessionStatus +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol UserVerificationSessionStatusCoordinatorDelegate: class { + func userVerificationSessionStatusCoordinator(_ coordinator: UserVerificationSessionStatusCoordinatorType, wantsToManuallyVerifyDeviceWithId deviceId: String, for userId: String) + func userVerificationSessionStatusCoordinatorDidClose(_ coordinator: UserVerificationSessionStatusCoordinatorType) +} + +/// `UserVerificationSessionStatusCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow. +protocol UserVerificationSessionStatusCoordinatorType: Coordinator, Presentable { + var delegate: UserVerificationSessionStatusCoordinatorDelegate? { get } +} diff --git a/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewAction.swift b/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewAction.swift new file mode 100644 index 0000000000..6c3f6b3457 --- /dev/null +++ b/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewAction.swift @@ -0,0 +1,26 @@ +// File created from ScreenTemplate +// $ createScreen.sh SessionStatus UserVerificationSessionStatus +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// UserVerificationSessionStatusViewController view actions exposed to view model +enum UserVerificationSessionStatusViewAction { + case loadData + case verify + case close +} diff --git a/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewController.storyboard b/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewController.storyboard new file mode 100644 index 0000000000..a24c78be79 --- /dev/null +++ b/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewController.storyboard @@ -0,0 +1,209 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewController.swift b/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewController.swift new file mode 100644 index 0000000000..8d32b38681 --- /dev/null +++ b/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewController.swift @@ -0,0 +1,267 @@ +// File created from ScreenTemplate +// $ createScreen.sh SessionStatus UserVerificationSessionStatus +/* + Copyright 2020 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 UserVerificationSessionStatusViewController: UIViewController { + + // MARK: - Constants + + private enum Constants { + static let verifyButtonCornerRadius: CGFloat = 8.0 + static let informationTextDefaultFont = UIFont.systemFont(ofSize: 15.0) + static let informationTextBoldFont = UIFont.systemFont(ofSize: 15.0, weight: .medium) + static let deviceNameFont = UIFont.systemFont(ofSize: 17.0, weight: .medium) + static let deviceIdFont = UIFont.systemFont(ofSize: 15.0) + } + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var badgeImageView: UIImageView! + @IBOutlet private weak var titleLabel: UILabel! + @IBOutlet private weak var closeButton: UIButton! + @IBOutlet private weak var informationLabel: UILabel! + + @IBOutlet private weak var deviceStatusImageView: UIImageView! + @IBOutlet private weak var deviceInformationLabel: UILabel! + + @IBOutlet private weak var untrustedSessionContainerView: UIView! + @IBOutlet private weak var untrustedSessionInformationLabel: UILabel! + @IBOutlet private weak var verifyButton: UIButton! + + // MARK: Private + + private var viewModel: UserVerificationSessionStatusViewModelType! + private var theme: Theme! + private var keyboardAvoider: KeyboardAvoider? + private var errorPresenter: MXKErrorPresentation! + private var activityPresenter: ActivityIndicatorPresenter! + + // MARK: - Setup + + class func instantiate(with viewModel: UserVerificationSessionStatusViewModelType) -> UserVerificationSessionStatusViewController { + let viewController = StoryboardScene.UserVerificationSessionStatusViewController.initialScene.instantiate() + viewController.viewModel = viewModel + viewController.theme = ThemeService.shared().theme + return viewController + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + self.setupViews() + self.activityPresenter = ActivityIndicatorPresenter() + self.errorPresenter = MXKErrorAlertPresentation() + + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + + self.viewModel.viewDelegate = self + self.viewModel.process(viewAction: .loadData) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.theme.statusBarStyle + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + self.verifyButton.layer.cornerRadius = Constants.verifyButtonCornerRadius + self.closeButton.layer.cornerRadius = self.closeButton.frame.size.width/2 + } + + // MARK: - Private + + private func update(theme: Theme) { + self.theme = theme + + if let navigationBar = self.navigationController?.navigationBar { + theme.applyStyle(onNavigationBar: navigationBar) + } + + self.view.backgroundColor = theme.headerBackgroundColor + + self.titleLabel.textColor = theme.textPrimaryColor + self.closeButton.vc_setBackgroundColor(theme.headerTextSecondaryColor, for: .normal) + + self.informationLabel.textColor = theme.textPrimaryColor + + self.untrustedSessionInformationLabel.textColor = theme.textPrimaryColor + self.verifyButton.vc_setBackgroundColor(theme.tintColor, for: .normal) + } + + 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.closeButton.layer.masksToBounds = true + self.verifyButton.layer.masksToBounds = true + } + + private func render(viewState: UserVerificationSessionStatusViewState) { + switch viewState { + case .loading: + self.renderLoading() + case .loaded(viewData: let sessionStatusViewData): + self.renderLoaded(viewData: sessionStatusViewData) + case .error(let error): + self.render(error: error) + } + } + + private func renderLoading() { + self.activityPresenter.presentActivityIndicator(on: self.view, animated: true) + } + + private func renderLoaded(viewData: SessionStatusViewData) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + + let badgeImage: UIImage + let title: String + + if viewData.isDeviceTrusted { + badgeImage = Asset.Images.encryptionTrusted.image + title = "Trusted" + } else { + badgeImage = Asset.Images.encryptionWarning.image + title = "Warning" + } + + let unstrustedInformationText: String + let verifyButtonTitle: String + + if viewData.isCurrentUser { + unstrustedInformationText = "If you didn’t sign in to this session, your account may be compromised." + verifyButtonTitle = "Verify" + } else { + unstrustedInformationText = "Until this user trusts this device, messages sent to and from it are labelled with warnings. Alternatively, you can manually verify it." + verifyButtonTitle = "Manually verify" + } + + self.badgeImageView.image = badgeImage + self.titleLabel.text = title + self.informationLabel.attributedText = self.buildInformationAttributedText(with: viewData) + + self.deviceStatusImageView.image = badgeImage + self.deviceInformationLabel.attributedText = self.builDeviceInfoAttributedText(with: viewData) + + self.untrustedSessionInformationLabel.text = unstrustedInformationText + self.verifyButton.setTitle(verifyButtonTitle, for: .normal) + } + + private func render(error: Error) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil) + } + + private func buildUserInfoText(with userId: String, userDisplayName: String?) -> String { + + let userInfoText: String + + if let userDisplayName = userDisplayName { + userInfoText = "\(userDisplayName) (\(userId))" + } else { + userInfoText = userId + } + + return userInfoText + } + + private func buildInformationAttributedText(with viewData: SessionStatusViewData) -> NSAttributedString { + + let informationAttributedText: NSMutableAttributedString = NSMutableAttributedString() + + let informationTextDefaultAttributes: [NSAttributedString.Key: Any] = [.foregroundColor: self.theme.textPrimaryColor, + .font: Constants.informationTextDefaultFont] + + let informationTextBoldAttributes: [NSAttributedString.Key: Any] = [.foregroundColor: self.theme.textPrimaryColor, + .font: Constants.informationTextBoldFont] + + let userInfoText = self.buildUserInfoText(with: viewData.userId, userDisplayName: viewData.userDisplayName) + + if viewData.isDeviceTrusted { + + if viewData.isCurrentUser { + let informationAttributedStringPart1 = NSAttributedString(string: "This session is trusted for secure messaging because you verified it:", attributes: informationTextDefaultAttributes) + informationAttributedText.append(informationAttributedStringPart1) + } else { + let informationAttributedStringPart1 = NSAttributedString(string: "This device is trusted for secure messaging because ", attributes: informationTextDefaultAttributes) + let informationAttributedStringPart2 = NSAttributedString(string: userInfoText, attributes: informationTextBoldAttributes) + let informationAttributedStringPart3 = NSAttributedString(string: " verified it:", attributes: informationTextDefaultAttributes) + + informationAttributedText.append(informationAttributedStringPart1) + informationAttributedText.append(informationAttributedStringPart2) + informationAttributedText.append(informationAttributedStringPart3) + } + + } else { + if viewData.isCurrentUser { + let informationAttributedStringPart1 = NSAttributedString(string: "Verify this session to mark it as trusted & grant it access to encrypted messages:", attributes: informationTextDefaultAttributes) + informationAttributedText.append(informationAttributedStringPart1) + } else { + let informationAttributedStringPart1 = NSAttributedString(string: userInfoText, attributes: informationTextBoldAttributes) + let informationAttributedStringPart2 = NSAttributedString(string: " signed in using a new device:", attributes: informationTextDefaultAttributes) + + informationAttributedText.append(informationAttributedStringPart1) + informationAttributedText.append(informationAttributedStringPart2) + } + } + + return informationAttributedText + } + + private func builDeviceInfoAttributedText(with viewData: SessionStatusViewData) -> NSAttributedString { + let deviceInfoAttributedText = NSMutableAttributedString() + let deviceInfoAttributedTextPart1 = NSAttributedString(string: "\(viewData.deviceName) ", attributes: [.foregroundColor: self.theme.textPrimaryColor, .font: Constants.deviceNameFont]) + let deviceInfoAttributedTextPart2 = NSAttributedString(string: "(\(viewData.deviceId))", attributes: [.foregroundColor: self.theme.textSecondaryColor, .font: Constants.deviceIdFont]) + deviceInfoAttributedText.append(deviceInfoAttributedTextPart1) + deviceInfoAttributedText.append(deviceInfoAttributedTextPart2) + return deviceInfoAttributedText + } + + // MARK: - Actions + + @IBAction private func closeButtonAction(_ sender: Any) { + self.viewModel.process(viewAction: .close) + } + + @IBAction private func verifyButtonAction(_ sender: Any) { + self.viewModel.process(viewAction: .verify) + } +} + + +// MARK: - UserVerificationSessionStatusViewModelViewDelegate +extension UserVerificationSessionStatusViewController: UserVerificationSessionStatusViewModelViewDelegate { + + func userVerificationSessionStatusViewModel(_ viewModel: UserVerificationSessionStatusViewModelType, didUpdateViewState viewSate: UserVerificationSessionStatusViewState) { + self.render(viewState: viewSate) + } +} diff --git a/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewModel.swift b/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewModel.swift new file mode 100644 index 0000000000..621652cf6a --- /dev/null +++ b/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewModel.swift @@ -0,0 +1,95 @@ +// File created from ScreenTemplate +// $ createScreen.sh SessionStatus UserVerificationSessionStatus +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +struct SessionStatusViewData { + let userId: String + let userDisplayName: String? + let isCurrentUser: Bool + + let deviceId: String + let deviceName: String + let isDeviceTrusted: Bool +} + +enum UserVerificationSessionStatusViewModelError: Error { + case deviceNotFound +} + +final class UserVerificationSessionStatusViewModel: UserVerificationSessionStatusViewModelType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let userId: String + private let userDisplayName: String? + private let deviceId: String + + // MARK: Public + + weak var viewDelegate: UserVerificationSessionStatusViewModelViewDelegate? + weak var coordinatorDelegate: UserVerificationSessionStatusViewModelCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, userId: String, userDisplayName: String?, deviceId: String) { + self.session = session + self.userId = userId + self.userDisplayName = userDisplayName + self.deviceId = deviceId + } + + // MARK: - Public + + func process(viewAction: UserVerificationSessionStatusViewAction) { + switch viewAction { + case .loadData: + self.loadData() + case .verify: + self.coordinatorDelegate?.userVerificationSessionStatusViewModel(self, wantsToManuallyVerifyDeviceWithId: self.deviceId, for: self.userId) + case .close: + self.coordinatorDelegate?.userVerificationSessionStatusViewModelDidClose(self) + } + } + + // MARK: - Private + + private func loadData() { + guard let deviceInfo = self.session.crypto.device(withDeviceId: self.deviceId, ofUser: self.userId) else { + self.update(viewState: .error(UserVerificationSessionStatusViewModelError.deviceNotFound)) + return + } + + let isCurrentUser = self.session.myUser.userId == self.userId + + let viewData = SessionStatusViewData(userId: self.userId, + userDisplayName: self.userDisplayName, + isCurrentUser: isCurrentUser, + deviceId: deviceInfo.deviceId, + deviceName: deviceInfo.displayName ?? "", + isDeviceTrusted: deviceInfo.trustLevel.isVerified) + self.update(viewState: .loaded(viewData: viewData)) + } + + private func update(viewState: UserVerificationSessionStatusViewState) { + self.viewDelegate?.userVerificationSessionStatusViewModel(self, didUpdateViewState: viewState) + } +} diff --git a/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewModelType.swift b/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewModelType.swift new file mode 100644 index 0000000000..178d0e4153 --- /dev/null +++ b/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewModelType.swift @@ -0,0 +1,37 @@ +// File created from ScreenTemplate +// $ createScreen.sh SessionStatus UserVerificationSessionStatus +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol UserVerificationSessionStatusViewModelViewDelegate: class { + func userVerificationSessionStatusViewModel(_ viewModel: UserVerificationSessionStatusViewModelType, didUpdateViewState viewSate: UserVerificationSessionStatusViewState) +} + +protocol UserVerificationSessionStatusViewModelCoordinatorDelegate: class { + func userVerificationSessionStatusViewModel(_ viewModel: UserVerificationSessionStatusViewModelType, wantsToManuallyVerifyDeviceWithId deviceId: String, for userId: String) + func userVerificationSessionStatusViewModelDidClose(_ viewModel: UserVerificationSessionStatusViewModelType) +} + +/// Protocol describing the view model used by `UserVerificationSessionStatusViewController` +protocol UserVerificationSessionStatusViewModelType { + + var viewDelegate: UserVerificationSessionStatusViewModelViewDelegate? { get set } + var coordinatorDelegate: UserVerificationSessionStatusViewModelCoordinatorDelegate? { get set } + + func process(viewAction: UserVerificationSessionStatusViewAction) +} diff --git a/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewState.swift b/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewState.swift new file mode 100644 index 0000000000..f1eb8f1c69 --- /dev/null +++ b/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewState.swift @@ -0,0 +1,26 @@ +// File created from ScreenTemplate +// $ createScreen.sh SessionStatus UserVerificationSessionStatus +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// UserVerificationSessionStatusViewController view state +enum UserVerificationSessionStatusViewState { + case loading + case loaded(viewData: SessionStatusViewData) + case error(Error) +} From 3b005d282d24d136d6f74acd5ae1002ac39a7b0a Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 30 Jan 2020 17:42:54 +0100 Subject: [PATCH 131/282] User verification: Create UserEncryptionTrustLevel enum. --- .../Members/Detail/UserEncryptionTrustLevel.h | 28 +++++++++++++++++++ Riot/SupportingFiles/Riot-Bridging-Header.h | 1 + 2 files changed, 29 insertions(+) create mode 100644 Riot/Modules/Room/Members/Detail/UserEncryptionTrustLevel.h diff --git a/Riot/Modules/Room/Members/Detail/UserEncryptionTrustLevel.h b/Riot/Modules/Room/Members/Detail/UserEncryptionTrustLevel.h new file mode 100644 index 0000000000..ae94714618 --- /dev/null +++ b/Riot/Modules/Room/Members/Detail/UserEncryptionTrustLevel.h @@ -0,0 +1,28 @@ +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +@import Foundation; + +/** + UserEncryptionTrustLevel represents the user trust level in an encrypted room. + */ +typedef NS_ENUM(NSUInteger, UserEncryptionTrustLevel) { + UserEncryptionTrustLevelTrusted, + UserEncryptionTrustLevelWarning, + UserEncryptionTrustLevelNormal, + UserEncryptionTrustLevelNone, + UserEncryptionTrustLevelUnknown +}; diff --git a/Riot/SupportingFiles/Riot-Bridging-Header.h b/Riot/SupportingFiles/Riot-Bridging-Header.h index c39876075e..5aeadc7d19 100644 --- a/Riot/SupportingFiles/Riot-Bridging-Header.h +++ b/Riot/SupportingFiles/Riot-Bridging-Header.h @@ -17,3 +17,4 @@ #import "AppDelegate.h" #import "RoomBubbleCellData.h" #import "MXKRoomBubbleTableViewCell+Riot.h" +#import "UserEncryptionTrustLevel.h" From 23a23445fa5f2cbae705e78e07c4b626f8a439a5 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 30 Jan 2020 17:48:22 +0100 Subject: [PATCH 132/282] Shields: Shields in one self room should be green if I have no other device --- Riot/Modules/Room/DataSources/RoomDataSource.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m index 93b77ad81c..175cff69af 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -245,7 +245,8 @@ - (void)encryptionTrustLevelDidChangeRelatedToUserId:(NSString*)userId forceDown double trustedDevicesPercentage = usersTrustLevelSummary.trustedDevicesProgress.fractionCompleted; - if (trustedDevicesPercentage >= 1.0) + if (trustedDevicesPercentage >= 1.0 + || usersTrustLevelSummary.trustedDevicesProgress.totalUnitCount == 0) { roomEncryptionTrustLevel = RoomEncryptionTrustLevelTrusted; } From ad5889bf2f43d53f3debc021ea5e276852d38d14 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 30 Jan 2020 17:54:03 +0100 Subject: [PATCH 133/282] User verification: Implement session list status screen. --- .../UserVerificationSessionStatusCell.swift | 79 ++++++ .../UserVerificationSessionStatusCell.xib | 78 ++++++ ...erificationSessionsStatusCoordinator.swift | 70 ++++++ ...icationSessionsStatusCoordinatorType.swift | 29 +++ ...VerificationSessionsStatusViewAction.swift | 26 ++ ...ionSessionsStatusViewController.storyboard | 109 +++++++++ ...ficationSessionsStatusViewController.swift | 230 ++++++++++++++++++ ...rVerificationSessionsStatusViewModel.swift | 148 +++++++++++ ...ificationSessionsStatusViewModelType.swift | 37 +++ ...rVerificationSessionsStatusViewState.swift | 26 ++ 10 files changed, 832 insertions(+) create mode 100644 Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionStatusCell.swift create mode 100644 Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionStatusCell.xib create mode 100644 Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusCoordinator.swift create mode 100644 Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusCoordinatorType.swift create mode 100644 Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewAction.swift create mode 100644 Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewController.storyboard create mode 100644 Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewController.swift create mode 100644 Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewModel.swift create mode 100644 Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewModelType.swift create mode 100644 Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewState.swift diff --git a/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionStatusCell.swift b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionStatusCell.swift new file mode 100644 index 0000000000..f281374f7a --- /dev/null +++ b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionStatusCell.swift @@ -0,0 +1,79 @@ +/* + Copyright 2020 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 + +struct UserVerificationSessionStatusViewData { + let deviceId: String + let sessionName: String + let isTrusted: Bool +} + +final class UserVerificationSessionStatusCell: UITableViewCell, NibReusable, Themable { + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var statusImageView: UIImageView! + @IBOutlet private weak var sessionNameLabel: UILabel! + @IBOutlet private weak var statusTextLabel: UILabel! + + // MARK: Private + + private var viewData: UserVerificationSessionStatusViewData? + private var theme: Theme? + + // MARK: - Public + + func fill(viewData: UserVerificationSessionStatusViewData) { + self.viewData = viewData + + let statusText: String + let statusImage: UIImage + + if viewData.isTrusted { + statusImage = Asset.Images.encryptionTrusted.image + statusText = "Trusted" + } else { + statusImage = Asset.Images.encryptionWarning.image + statusText = "Not trusted" + } + + self.statusImageView.image = statusImage + self.statusTextLabel.text = statusText + self.sessionNameLabel.text = viewData.sessionName + + self.updateStatusTextColor() + } + + func update(theme: Theme) { + self.theme = theme + self.backgroundColor = theme.headerBackgroundColor + self.sessionNameLabel.textColor = theme.textPrimaryColor + self.updateStatusTextColor() + } + + // MARK: - Private + + private func updateStatusTextColor() { + guard let viewData = self.viewData, let theme = self.theme else { + return + } + self.statusTextLabel.textColor = viewData.isTrusted ? theme.tintColor : theme.warningColor + } +} diff --git a/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionStatusCell.xib b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionStatusCell.xib new file mode 100644 index 0000000000..0ee3f32314 --- /dev/null +++ b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionStatusCell.xib @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusCoordinator.swift b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusCoordinator.swift new file mode 100644 index 0000000000..b2d19c91cd --- /dev/null +++ b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusCoordinator.swift @@ -0,0 +1,70 @@ +// File created from ScreenTemplate +// $ createScreen.sh UserVerification UserVerificationSessionsStatus +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation +import UIKit + +final class UserVerificationSessionsStatusCoordinator: UserVerificationSessionsStatusCoordinatorType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private var userVerificationSessionsStatusViewModel: UserVerificationSessionsStatusViewModelType + private let userVerificationSessionsStatusViewController: UserVerificationSessionsStatusViewController + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: UserVerificationSessionsStatusCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, userId: String) { + self.session = session + + let userVerificationSessionsStatusViewModel = UserVerificationSessionsStatusViewModel(session: self.session, userId: userId) + let userVerificationSessionsStatusViewController = UserVerificationSessionsStatusViewController.instantiate(with: userVerificationSessionsStatusViewModel) + self.userVerificationSessionsStatusViewModel = userVerificationSessionsStatusViewModel + self.userVerificationSessionsStatusViewController = userVerificationSessionsStatusViewController + } + + // MARK: - Public methods + + func start() { + self.userVerificationSessionsStatusViewModel.coordinatorDelegate = self + } + + func toPresentable() -> UIViewController { + return self.userVerificationSessionsStatusViewController + } +} + +// MARK: - UserVerificationSessionsStatusViewModelCoordinatorDelegate +extension UserVerificationSessionsStatusCoordinator: UserVerificationSessionsStatusViewModelCoordinatorDelegate { + func userVerificationSessionsStatusViewModel(_ viewModel: UserVerificationSessionsStatusViewModelType, didSelectDeviceWithId deviceId: String, for userId: String) { + self.delegate?.userVerificationSessionsStatusCoordinator(self, didSelectDeviceWithId: deviceId, for: userId) + } + + func userVerificationSessionsStatusViewModelDidClose(_ viewModel: UserVerificationSessionsStatusViewModelType) { + self.delegate?.userVerificationSessionsStatusCoordinatorDidClose(self) + } +} diff --git a/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusCoordinatorType.swift b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusCoordinatorType.swift new file mode 100644 index 0000000000..46eccaaa5e --- /dev/null +++ b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusCoordinatorType.swift @@ -0,0 +1,29 @@ +// File created from ScreenTemplate +// $ createScreen.sh UserVerification UserVerificationSessionsStatus +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol UserVerificationSessionsStatusCoordinatorDelegate: class { + func userVerificationSessionsStatusCoordinatorDidClose(_ coordinator: UserVerificationSessionsStatusCoordinatorType) + func userVerificationSessionsStatusCoordinator(_ coordinator: UserVerificationSessionsStatusCoordinatorType, didSelectDeviceWithId deviceId: String, for userId: String) +} + +/// `UserVerificationSessionsStatusCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow. +protocol UserVerificationSessionsStatusCoordinatorType: Coordinator, Presentable { + var delegate: UserVerificationSessionsStatusCoordinatorDelegate? { get } +} diff --git a/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewAction.swift b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewAction.swift new file mode 100644 index 0000000000..8487612ecb --- /dev/null +++ b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewAction.swift @@ -0,0 +1,26 @@ +// File created from ScreenTemplate +// $ createScreen.sh UserVerification UserVerificationSessionsStatus +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// UserVerificationSessionsStatusViewController view actions exposed to view model +enum UserVerificationSessionsStatusViewAction { + case loadData + case selectSession(deviceId: String) + case close +} diff --git a/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewController.storyboard b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewController.storyboard new file mode 100644 index 0000000000..2d5c915f01 --- /dev/null +++ b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewController.storyboard @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewController.swift b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewController.swift new file mode 100644 index 0000000000..f99a7e107c --- /dev/null +++ b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewController.swift @@ -0,0 +1,230 @@ +// File created from ScreenTemplate +// $ createScreen.sh UserVerification UserVerificationSessionsStatus +/* + Copyright 2020 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 UserVerificationSessionsStatusViewController: UIViewController { + + // MARK: - Constants + + private enum Constants { + static let estimatedRowHeight: CGFloat = 40.0 + } + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var badgeImageImageView: UIImageView! + @IBOutlet private weak var titleLabel: UILabel! + @IBOutlet private weak var closeButton: UIButton! + @IBOutlet private weak var informationLabel: UILabel! + @IBOutlet private weak var tableView: UITableView! + + // MARK: Private + + private var viewModel: UserVerificationSessionsStatusViewModelType! + private var theme: Theme! + private var errorPresenter: MXKErrorPresentation! + private var activityIndicatorPresenter: ActivityIndicatorPresenter! + private var sessionsStatusViewData: [UserVerificationSessionStatusViewData] = [] + private var userEncryptionTrustLevel: UserEncryptionTrustLevel = .unknown + + // MARK: - Setup + + class func instantiate(with viewModel: UserVerificationSessionsStatusViewModelType) -> UserVerificationSessionsStatusViewController { + let viewController = StoryboardScene.UserVerificationSessionsStatusViewController.initialScene.instantiate() + viewController.viewModel = viewModel + viewController.theme = ThemeService.shared().theme + return viewController + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + self.setupViews() + self.activityIndicatorPresenter = ActivityIndicatorPresenter() + self.errorPresenter = MXKErrorAlertPresentation() + + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + + self.viewModel.viewDelegate = self + + self.viewModel.process(viewAction: .loadData) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.theme.statusBarStyle + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + self.closeButton.layer.cornerRadius = self.closeButton.frame.size.width/2 + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + if let selectedIndexPath = self.tableView.indexPathForSelectedRow { + self.tableView.deselectRow(at: selectedIndexPath, animated: animated) + } + } + + // MARK: - Private + + private func update(theme: Theme) { + self.theme = theme + + self.view.backgroundColor = theme.headerBackgroundColor + + if let navigationBar = self.navigationController?.navigationBar { + theme.applyStyle(onNavigationBar: navigationBar) + } + + self.closeButton.vc_setBackgroundColor(theme.headerTextSecondaryColor, for: .normal) + self.titleLabel.textColor = theme.textPrimaryColor + self.informationLabel.textColor = theme.textPrimaryColor + } + + 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.closeButton.layer.masksToBounds = true + self.setupTableView() + self.updateTitleViews() + + self.informationLabel.text = "Messages with this user in this room are end-to-end encrypted and can’t be read by third parties." + } + + private func setupTableView() { + self.tableView.rowHeight = UITableView.automaticDimension + self.tableView.estimatedRowHeight = Constants.estimatedRowHeight + self.tableView.separatorStyle = .none + self.tableView.tableFooterView = UIView() + self.tableView.alwaysBounceVertical = false + + self.tableView.register(cellType: UserVerificationSessionStatusCell.self) + } + + private func render(viewState: UserVerificationSessionsStatusViewState) { + switch viewState { + case .loading: + self.renderLoading() + case .loaded(userTrustLevel: let userTrustLevel, sessionsStatusViewData: let sessionsStatusViewData): + self.renderLoaded(userTrustLevel: userTrustLevel, sessionsStatusViewData: sessionsStatusViewData) + case .error(let error): + self.render(error: error) + } + } + + private func renderLoading() { + self.tableView.isUserInteractionEnabled = false + self.activityIndicatorPresenter.presentActivityIndicator(on: self.view, animated: true) + } + + private func renderLoaded(userTrustLevel: UserEncryptionTrustLevel, sessionsStatusViewData: [UserVerificationSessionStatusViewData]) { + self.activityIndicatorPresenter.removeCurrentActivityIndicator(animated: true) + self.tableView.isUserInteractionEnabled = true + + self.userEncryptionTrustLevel = userTrustLevel + self.sessionsStatusViewData = sessionsStatusViewData + + self.updateTitleViews() + self.tableView.reloadData() + } + + private func render(error: Error) { + self.activityIndicatorPresenter.removeCurrentActivityIndicator(animated: true) + self.tableView.isUserInteractionEnabled = true + self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil) + } + + private func updateTitleViews() { + + let badgeImage: UIImage + let title: String + + switch self.userEncryptionTrustLevel { + case .trusted: + badgeImage = Asset.Images.encryptionTrusted.image + title = "Trusted" + case .warning: + badgeImage = Asset.Images.encryptionWarning.image + title = "Warning" + default: + badgeImage = Asset.Images.encryptionNormal.image + title = "Unknown" + } + + self.badgeImageImageView.image = badgeImage + self.titleLabel.text = title + } + + // MARK: - Actions + + @IBAction private func closeButtonAction(_ sender: Any) { + self.viewModel.process(viewAction: .close) + } +} + +// MARK: - UITableViewDataSource +extension UserVerificationSessionsStatusViewController: UITableViewDataSource { + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return self.sessionsStatusViewData.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(for: indexPath, cellType: UserVerificationSessionStatusCell.self) + + let viewData = self.sessionsStatusViewData[indexPath.row] + + cell.update(theme: self.theme) + cell.fill(viewData: viewData) + + return cell + } +} + +// MARK: - UITableViewDelegate +extension UserVerificationSessionsStatusViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let viewData = self.sessionsStatusViewData[indexPath.row] + self.viewModel.process(viewAction: .selectSession(deviceId: viewData.deviceId)) + } +} + +// MARK: - UserVerificationSessionsStatusViewModelViewDelegate +extension UserVerificationSessionsStatusViewController: UserVerificationSessionsStatusViewModelViewDelegate { + + func userVerificationSessionsStatusViewModel(_ viewModel: UserVerificationSessionsStatusViewModelType, didUpdateViewState viewSate: UserVerificationSessionsStatusViewState) { + self.render(viewState: viewSate) + } +} diff --git a/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewModel.swift b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewModel.swift new file mode 100644 index 0000000000..27ecd1c091 --- /dev/null +++ b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewModel.swift @@ -0,0 +1,148 @@ +// File created from ScreenTemplate +// $ createScreen.sh UserVerification UserVerificationSessionsStatus +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +enum UserVerificationSessionsStatusViewModelError: Error { + case unknown +} + +final class UserVerificationSessionsStatusViewModel: UserVerificationSessionsStatusViewModelType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let userId: String + private var currentOperation: MXHTTPOperation? + private var userTrustLevel: UserEncryptionTrustLevel + + // MARK: Public + + weak var viewDelegate: UserVerificationSessionsStatusViewModelViewDelegate? + weak var coordinatorDelegate: UserVerificationSessionsStatusViewModelCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, userId: String) { + self.session = session + self.userId = userId + self.userTrustLevel = .unknown + } + + deinit { + self.currentOperation?.cancel() + } + + // MARK: - Public + + func process(viewAction: UserVerificationSessionsStatusViewAction) { + switch viewAction { + case .loadData: + self.loadData() + case .selectSession(deviceId: let deviceId): + self.coordinatorDelegate?.userVerificationSessionsStatusViewModel(self, didSelectDeviceWithId: deviceId, for: self.userId) + case .close: + self.coordinatorDelegate?.userVerificationSessionsStatusViewModelDidClose(self) + } + } + + // MARK: - Private + + private func loadData() { + + let sessionsStatusViewData = self.getSessionStatusViewDataListFromCache(for: self.userId) + self.update(viewState: .loaded(userTrustLevel: self.userTrustLevel, sessionsStatusViewData: sessionsStatusViewData)) + + self.fetchSessionStatus() + } + + private func update(viewState: UserVerificationSessionsStatusViewState) { + self.viewDelegate?.userVerificationSessionsStatusViewModel(self, didUpdateViewState: viewState) + } + + private func fetchSessionStatus() { + self.update(viewState: .loading) + + self.currentOperation = self.getSessionStatusViewDataList(for: self.userId) { result in + switch result { + case .success(let sessionsStatusViewData): + + let isUserTrusted = sessionsStatusViewData.contains(where: { sessionsStatusViewData -> Bool in + return sessionsStatusViewData.isTrusted == false + }) == false + + let userTrustLevel: UserEncryptionTrustLevel = isUserTrusted ? .trusted : .warning + + self.update(viewState: .loaded(userTrustLevel: userTrustLevel, sessionsStatusViewData: sessionsStatusViewData)) + case .failure(let error): + self.update(viewState: .error(error)) + } + } + } + + private func getSessionStatusViewDataListFromCache(for userId: String) -> [UserVerificationSessionStatusViewData] { + let deviceInfoList = self.getDevicesFromCache(for: self.userId) + return self.sessionStatusViewDataList(from: deviceInfoList) + } + + private func getDevicesFromCache(for userId: String) -> [MXDeviceInfo] { + guard let deviceInfoMap = self.session.crypto.devices(forUser: self.userId) else { + return [] + } + return Array(deviceInfoMap.values) + } + + @discardableResult + private func getSessionStatusViewDataList(for userId: String, completion: @escaping (Result<[UserVerificationSessionStatusViewData], Error>) -> Void) -> MXHTTPOperation? { + + let httpOperation: MXHTTPOperation? + + httpOperation = self.session.crypto.downloadKeys([self.userId], forceDownload: false, success: { ( usersDeviceMap: MXUsersDevicesMap?, usersCrossSigningMap: [String : MXCrossSigningInfo]?) in + + let sessionsViewData: [UserVerificationSessionStatusViewData] + + if let usersDeviceMap = usersDeviceMap, let userDeviceInfoMap = Array(usersDeviceMap.map.values).first { + let deviceInfoList = Array(userDeviceInfoMap.values) + sessionsViewData = self.sessionStatusViewDataList(from: deviceInfoList) + } else { + sessionsViewData = [] + } + + completion(.success(sessionsViewData)) + + }, failure: { error in + + let finalError = error ?? UserVerificationSessionsStatusViewModelError.unknown + completion(.failure(finalError)) + }) + + return httpOperation + } + + private func sessionStatusViewData(from deviceInfo: MXDeviceInfo) -> UserVerificationSessionStatusViewData { + return UserVerificationSessionStatusViewData(deviceId: deviceInfo.deviceId, sessionName: deviceInfo.displayName ?? "", isTrusted: deviceInfo.trustLevel.isVerified) + } + + private func sessionStatusViewDataList(from deviceInfoList: [MXDeviceInfo]) -> [UserVerificationSessionStatusViewData] { + return deviceInfoList.map { (deviceInfo) -> UserVerificationSessionStatusViewData in + return self.sessionStatusViewData(from: deviceInfo) + } + } +} diff --git a/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewModelType.swift b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewModelType.swift new file mode 100644 index 0000000000..4df41221d2 --- /dev/null +++ b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewModelType.swift @@ -0,0 +1,37 @@ +// File created from ScreenTemplate +// $ createScreen.sh UserVerification UserVerificationSessionsStatus +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol UserVerificationSessionsStatusViewModelViewDelegate: class { + func userVerificationSessionsStatusViewModel(_ viewModel: UserVerificationSessionsStatusViewModelType, didUpdateViewState viewSate: UserVerificationSessionsStatusViewState) +} + +protocol UserVerificationSessionsStatusViewModelCoordinatorDelegate: class { + func userVerificationSessionsStatusViewModel(_ viewModel: UserVerificationSessionsStatusViewModelType, didSelectDeviceWithId deviceId: String, for userId: String) + func userVerificationSessionsStatusViewModelDidClose(_ viewModel: UserVerificationSessionsStatusViewModelType) +} + +/// Protocol describing the view model used by `UserVerificationSessionsStatusViewController` +protocol UserVerificationSessionsStatusViewModelType { + + var viewDelegate: UserVerificationSessionsStatusViewModelViewDelegate? { get set } + var coordinatorDelegate: UserVerificationSessionsStatusViewModelCoordinatorDelegate? { get set } + + func process(viewAction: UserVerificationSessionsStatusViewAction) +} diff --git a/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewState.swift b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewState.swift new file mode 100644 index 0000000000..742938c023 --- /dev/null +++ b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewState.swift @@ -0,0 +1,26 @@ +// File created from ScreenTemplate +// $ createScreen.sh UserVerification UserVerificationSessionsStatus +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// UserVerificationSessionsStatusViewController view state +enum UserVerificationSessionsStatusViewState { + case loading + case loaded(userTrustLevel: UserEncryptionTrustLevel, sessionsStatusViewData: [UserVerificationSessionStatusViewData]) + case error(Error) +} From 4e6f5a0800b1ffc86b8bdd9753b0281518935e84 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 30 Jan 2020 17:55:36 +0100 Subject: [PATCH 134/282] User verification: Implement user session status flow coordinator. --- .../UserVerificationCoordinator.swift | 167 ++++++++++++++++++ ...rificationCoordinatorBridgePresenter.swift | 66 +++++++ .../UserVerificationCoordinatorType.swift | 28 +++ 3 files changed, 261 insertions(+) create mode 100644 Riot/Modules/UserVerification/UserVerificationCoordinator.swift create mode 100644 Riot/Modules/UserVerification/UserVerificationCoordinatorBridgePresenter.swift create mode 100644 Riot/Modules/UserVerification/UserVerificationCoordinatorType.swift diff --git a/Riot/Modules/UserVerification/UserVerificationCoordinator.swift b/Riot/Modules/UserVerification/UserVerificationCoordinator.swift new file mode 100644 index 0000000000..5bda5e45f6 --- /dev/null +++ b/Riot/Modules/UserVerification/UserVerificationCoordinator.swift @@ -0,0 +1,167 @@ +// File created from FlowTemplate +// $ createRootCoordinator.sh UserVerification UserVerification +/* + Copyright 2020 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 + +@objcMembers +final class UserVerificationCoordinator: NSObject, UserVerificationCoordinatorType { + + // MARK: - Properties + + // MARK: Private + + private let presenter: Presentable + private let navigationRouter: NavigationRouterType + private let session: MXSession + private let userId: String + private let userDisplayName: String? + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: UserVerificationCoordinatorDelegate? + + // MARK: - Setup + + init(presenter: Presentable, session: MXSession, userId: String, userDisplayName: String?) { + self.presenter = presenter + self.navigationRouter = NavigationRouter(navigationController: RiotNavigationController()) + self.session = session + self.userId = userId + self.userDisplayName = userDisplayName + } + + // MARK: - Public methods + + func start() { + // Do not start again if existing coordinators are presented + guard self.childCoordinators.isEmpty else { + return + } + + let rootCoordinator = UserVerificationSessionsStatusCoordinator(session: self.session, userId: self.userId) + rootCoordinator.delegate = self + rootCoordinator.start() + + self.add(childCoordinator: rootCoordinator) + + self.navigationRouter.setRootModule(rootCoordinator, hideNavigationBar: true, animated: false, popCompletion: { + self.remove(childCoordinator: rootCoordinator) + }) + + let rootViewController = self.navigationRouter.toPresentable() + rootViewController.modalPresentationStyle = .formSheet + + self.presenter.toPresentable().present(rootViewController, animated: true, completion: nil) + } + + func toPresentable() -> UIViewController { + return self.navigationRouter.toPresentable() + } + + // MARK: - Private methods + + private func presentSessionStatus(with deviceId: String, for userId: String, userDisplayName: String?) { + let coordinator = UserVerificationSessionStatusCoordinator(session: self.session, userId: userId, userDisplayName: userDisplayName, deviceId: deviceId) + coordinator.delegate = self + coordinator.start() + + self.navigationRouter.push(coordinator, animated: true) { + self.remove(childCoordinator: coordinator) + } + } + + private func presentDeviceVerification(for deviceId: String) { + + guard let deviceInfo = self.session.crypto.device(withDeviceId: deviceId, ofUser: self.userId) else { + NSLog("[UserVerificationCoordinator] Device not found") + return + } + + let encryptionInfoView: EncryptionInfoView = EncryptionInfoView(deviceInfo: deviceInfo, andMatrixSession: session) + encryptionInfoView.delegate = self + + // Skip the intro page + encryptionInfoView.displayLegacyVerificationScreen() + + // Display the legacy verification view in full screen + // TODO: Do not reuse the legacy EncryptionInfoView and create a screen from scratch + let viewController = UIViewController() + + viewController.view.backgroundColor = ThemeService.shared().theme.backgroundColor + viewController.view.addSubview(encryptionInfoView) + encryptionInfoView.translatesAutoresizingMaskIntoConstraints = false + + let superViewMargins = viewController.view.layoutMarginsGuide + + NSLayoutConstraint.activate([ + encryptionInfoView.topAnchor.constraint(equalTo: superViewMargins.topAnchor), + encryptionInfoView.leadingAnchor.constraint(equalTo: superViewMargins.leadingAnchor), + encryptionInfoView.trailingAnchor.constraint(equalTo: superViewMargins.trailingAnchor), + encryptionInfoView.bottomAnchor.constraint(equalTo: superViewMargins.bottomAnchor) + ]) + + self.navigationRouter.push(viewController, animated: true, popCompletion: nil) + } +} + +// MARK: - UserVerificationSessionsStatusCoordinatorDelegate +extension UserVerificationCoordinator: UserVerificationSessionsStatusCoordinatorDelegate { + func userVerificationSessionsStatusCoordinatorDidClose(_ coordinator: UserVerificationSessionsStatusCoordinatorType) { + + self.presenter.toPresentable().dismiss(animated: true) { + self.remove(childCoordinator: coordinator) + } + } + + func userVerificationSessionsStatusCoordinator(_ coordinator: UserVerificationSessionsStatusCoordinatorType, didSelectDeviceWithId deviceId: String, for userId: String) { + self.presentSessionStatus(with: deviceId, for: userId, userDisplayName: self.userDisplayName) + } +} + +// MARK: - UserVerificationSessionStatusCoordinatorDelegate +extension UserVerificationCoordinator: UserVerificationSessionStatusCoordinatorDelegate { + + func userVerificationSessionStatusCoordinator(_ coordinator: UserVerificationSessionStatusCoordinatorType, wantsToManuallyVerifyDeviceWithId deviceId: String, for userId: String) { + + self.presentDeviceVerification(for: deviceId) + } + + func userVerificationSessionStatusCoordinatorDidClose(_ coordinator: UserVerificationSessionStatusCoordinatorType) { + + self.presenter.toPresentable().dismiss(animated: true) { + self.remove(childCoordinator: coordinator) + } + } +} + +// MARK: - MXKEncryptionInfoViewDelegate +extension UserVerificationCoordinator: MXKEncryptionInfoViewDelegate { + func encryptionInfoView(_ encryptionInfoView: MXKEncryptionInfoView!, didDeviceInfoVerifiedChange deviceInfo: MXDeviceInfo!) { + + self.presenter.toPresentable().dismiss(animated: true) { + } + } + + func encryptionInfoViewDidClose(_ encryptionInfoView: MXKEncryptionInfoView!) { + self.presenter.toPresentable().dismiss(animated: true) { + } + } +} diff --git a/Riot/Modules/UserVerification/UserVerificationCoordinatorBridgePresenter.swift b/Riot/Modules/UserVerification/UserVerificationCoordinatorBridgePresenter.swift new file mode 100644 index 0000000000..929449c773 --- /dev/null +++ b/Riot/Modules/UserVerification/UserVerificationCoordinatorBridgePresenter.swift @@ -0,0 +1,66 @@ +// File created from FlowTemplate +// $ createRootCoordinator.sh UserVerification UserVerification +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +@objc protocol UserVerificationCoordinatorBridgePresenterDelegate { + func userVerificationCoordinatorBridgePresenterDelegateDidComplete(_ coordinatorBridgePresenter: UserVerificationCoordinatorBridgePresenter) +} + +/// UserVerificationCoordinatorBridgePresenter enables to start UserVerificationCoordinator from a view controller. +/// This bridge is used while waiting for global usage of coordinator pattern. +@objcMembers +final class UserVerificationCoordinatorBridgePresenter: NSObject { + + // MARK: - Properties + + // MARK: Private + + private let presenter: Presentable + private let session: MXSession + private let userId: String + private let userDisplayName: String? + + private var coordinator: Coordinator? + + // MARK: Public + + weak var delegate: UserVerificationCoordinatorBridgePresenterDelegate? + + // MARK: - Setup + + init(presenter: UIViewController, session: MXSession, userId: String, userDisplayName: String?) { + self.presenter = presenter + self.session = session + self.userId = userId + self.userDisplayName = userDisplayName + super.init() + } + + // MARK: - Public + + func start() { + self.present() + } + + func present() { + let userVerificationCoordinator = UserVerificationCoordinator(presenter: self.presenter, session: self.session, userId: self.userId, userDisplayName: self.userDisplayName) + userVerificationCoordinator.start() + self.coordinator = userVerificationCoordinator + } +} diff --git a/Riot/Modules/UserVerification/UserVerificationCoordinatorType.swift b/Riot/Modules/UserVerification/UserVerificationCoordinatorType.swift new file mode 100644 index 0000000000..feeba8d3bb --- /dev/null +++ b/Riot/Modules/UserVerification/UserVerificationCoordinatorType.swift @@ -0,0 +1,28 @@ +// File created from FlowTemplate +// $ createRootCoordinator.sh UserVerification UserVerification +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol UserVerificationCoordinatorDelegate: class { + func userVerificationCoordinatorDidComplete(_ coordinator: UserVerificationCoordinatorType) +} + +/// `UserVerificationCoordinatorType` is a protocol describing a Coordinator that handle user verification navigation flow. +protocol UserVerificationCoordinatorType: Coordinator, Presentable { + var delegate: UserVerificationCoordinatorDelegate? { get } +} From 6e25793c0a11e8cb55ce88a5f4c7472fec02c6f7 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 30 Jan 2020 17:57:09 +0100 Subject: [PATCH 135/282] User verification: Add start user verification flow to DeviceVerificationCoordinator. --- .../DeviceVerificationCoordinator.swift | 43 ++++++++++++++++++- ...rificationCoordinatorBridgePresenter.swift | 12 ++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/Riot/Modules/DeviceVerification/DeviceVerificationCoordinator.swift b/Riot/Modules/DeviceVerification/DeviceVerificationCoordinator.swift index 84dca96669..b3ffdc89e0 100644 --- a/Riot/Modules/DeviceVerification/DeviceVerificationCoordinator.swift +++ b/Riot/Modules/DeviceVerification/DeviceVerificationCoordinator.swift @@ -32,6 +32,8 @@ final class DeviceVerificationCoordinator: DeviceVerificationCoordinatorType { private var incomingTransaction: MXIncomingSASTransaction? private var incomingKeyVerificationRequest: MXKeyVerificationRequest? + + var roomMember: MXRoomMember? // MARK: Public @@ -77,13 +79,28 @@ final class DeviceVerificationCoordinator: DeviceVerificationCoordinatorType { self.incomingKeyVerificationRequest = incomingKeyVerificationRequest } + /// Constructor to start a user verification. + /// + /// - Parameters: + /// - session: the MXSession + /// - roomMember: an other room member + init(session: MXSession, roomMember: MXRoomMember) { + self.navigationRouter = NavigationRouter(navigationController: RiotNavigationController()) + self.session = session + self.otherUserId = roomMember.userId + self.otherDeviceId = "" + self.roomMember = roomMember + } + // MARK: - Public methods func start() { - let rootCoordinator: Coordinator & Presentable - + let rootCoordinator: Coordinator & Presentable + if let incomingKeyVerificationRequest = self.incomingKeyVerificationRequest { rootCoordinator = self.createDataLoadingScreenCoordinator(with: incomingKeyVerificationRequest) + } else if let roomMember = self.roomMember { + rootCoordinator = self.createUserVerificationStartCoordinator(with: roomMember) } else { rootCoordinator = self.createDataLoadingScreenCoordinator() } @@ -117,6 +134,14 @@ final class DeviceVerificationCoordinator: DeviceVerificationCoordinatorType { return coordinator } + + private func createUserVerificationStartCoordinator(with roomMember: MXRoomMember) -> UserVerificationStartCoordinator { + let coordinator = UserVerificationStartCoordinator(session: self.session, roomMember: roomMember) + coordinator.delegate = self + coordinator.start() + + return coordinator + } private func showStart(otherUser: MXUser, otherDevice: MXDeviceInfo) { let coordinator = DeviceVerificationStartCoordinator(session: self.session, otherUser: otherUser, otherDevice: otherDevice) @@ -226,3 +251,17 @@ extension DeviceVerificationCoordinator: DeviceVerificationVerifiedViewControlle self.delegate?.deviceVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId) } } + +extension DeviceVerificationCoordinator: UserVerificationStartCoordinatorDelegate { + func userVerificationStartCoordinator(_ coordinator: UserVerificationStartCoordinatorType, didCompleteWithOutgoingTransaction transaction: MXSASTransaction) { + self.showVerify(transaction: transaction, animated: true) + } + + func userVerificationStartCoordinator(_ coordinator: UserVerificationStartCoordinatorType, didTransactionCancelled transaction: MXSASTransaction) { + self.delegate?.deviceVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId) + } + + func userVerificationStartCoordinatorDidCancel(_ coordinator: UserVerificationStartCoordinatorType) { + self.delegate?.deviceVerificationCoordinatorDidComplete(self, otherUserId: self.otherUserId, otherDeviceId: self.otherDeviceId) + } +} diff --git a/Riot/Modules/DeviceVerification/DeviceVerificationCoordinatorBridgePresenter.swift b/Riot/Modules/DeviceVerification/DeviceVerificationCoordinatorBridgePresenter.swift index bdd170fb6a..8176b655b5 100644 --- a/Riot/Modules/DeviceVerification/DeviceVerificationCoordinatorBridgePresenter.swift +++ b/Riot/Modules/DeviceVerification/DeviceVerificationCoordinatorBridgePresenter.swift @@ -63,6 +63,18 @@ final class DeviceVerificationCoordinatorBridgePresenter: NSObject { self.coordinator = deviceVerificationCoordinator } + + func present(from viewController: UIViewController, roomMember: MXRoomMember, animated: Bool) { + + NSLog("[DeviceVerificationCoordinatorBridgePresenter] Present from \(viewController)") + + let deviceVerificationCoordinator = DeviceVerificationCoordinator(session: self.session, roomMember: roomMember) + deviceVerificationCoordinator.delegate = self + viewController.present(deviceVerificationCoordinator.toPresentable(), animated: animated, completion: nil) + deviceVerificationCoordinator.start() + + self.coordinator = deviceVerificationCoordinator + } func present(from viewController: UIViewController, incomingTransaction: MXIncomingSASTransaction, animated: Bool) { From 86036867d63d7c3c532d80589d11ec007f66210b Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 30 Jan 2020 17:59:42 +0100 Subject: [PATCH 136/282] User verification: Handle start user verification flow through AppDelegate. --- Riot/AppDelegate.h | 2 ++ Riot/AppDelegate.m | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/Riot/AppDelegate.h b/Riot/AppDelegate.h index 70984e2b9c..b3091d4bc0 100644 --- a/Riot/AppDelegate.h +++ b/Riot/AppDelegate.h @@ -140,6 +140,8 @@ extern NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey; - (BOOL)presentIncomingKeyVerificationRequest:(MXKeyVerificationRequest*)incomingKeyVerificationRequest inSession:(MXSession*)session; +- (BOOL)presentUserVerificationForRoomMember:(MXRoomMember*)roomMember roomId:(NSString*)roomId session:(MXSession*)mxSession; + #pragma mark - Matrix Accounts handling - (void)selectMatrixAccount:(void (^)(MXKAccount *selectedAccount))onSelection; diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index ddde73e322..35b5236cd1 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -4889,6 +4889,29 @@ - (BOOL)presentIncomingDeviceVerification:(MXIncomingSASTransaction*)transaction return presented; } +- (BOOL)presentUserVerificationForRoomMember:(MXRoomMember*)roomMember session:(MXSession*)mxSession +{ + NSLog(@"[AppDelegate][MXKeyVerification] presentUserVerificationForRoomMember: %@", roomMember); + + BOOL presented = NO; + if (!deviceVerificationCoordinatorBridgePresenter) + { + UIViewController *presentingViewController = self.window.rootViewController.presentedViewController ?: self.window.rootViewController; + + deviceVerificationCoordinatorBridgePresenter = [[DeviceVerificationCoordinatorBridgePresenter alloc] initWithSession:mxSession]; + deviceVerificationCoordinatorBridgePresenter.delegate = self; + + [deviceVerificationCoordinatorBridgePresenter presentFrom:presentingViewController roomMember:roomMember animated:mxSession]; + + presented = YES; + } + else + { + NSLog(@"[AppDelegate][MXKeyVerification] presentUserVerificationForRoomMember: Controller already presented."); + } + return presented; +} + - (void)deviceVerificationCoordinatorBridgePresenterDelegateDidComplete:(DeviceVerificationCoordinatorBridgePresenter *)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId { [deviceVerificationCoordinatorBridgePresenter dismissWithAnimated:YES completion:^{ From 0d23b2d46132caf3b26a259a9f8bb3b674a21d5c Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 30 Jan 2020 18:05:41 +0100 Subject: [PATCH 137/282] User verification: Update room member details screen with security section and shields. --- Riot/AppDelegate.h | 2 +- .../Details/Views/RoomTableViewCell.xib | 9 +- .../Detail/RoomMemberDetailsViewController.h | 14 +- .../Detail/RoomMemberDetailsViewController.m | 320 +++++++++++++++--- .../RoomMemberDetailsViewController.xib | 18 +- 5 files changed, 292 insertions(+), 71 deletions(-) diff --git a/Riot/AppDelegate.h b/Riot/AppDelegate.h index b3091d4bc0..33f2ade6de 100644 --- a/Riot/AppDelegate.h +++ b/Riot/AppDelegate.h @@ -140,7 +140,7 @@ extern NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey; - (BOOL)presentIncomingKeyVerificationRequest:(MXKeyVerificationRequest*)incomingKeyVerificationRequest inSession:(MXSession*)session; -- (BOOL)presentUserVerificationForRoomMember:(MXRoomMember*)roomMember roomId:(NSString*)roomId session:(MXSession*)mxSession; +- (BOOL)presentUserVerificationForRoomMember:(MXRoomMember*)roomMember session:(MXSession*)mxSession; #pragma mark - Matrix Accounts handling diff --git a/Riot/Modules/Contacts/Details/Views/RoomTableViewCell.xib b/Riot/Modules/Contacts/Details/Views/RoomTableViewCell.xib index 2666caf2f4..633ae0a98f 100644 --- a/Riot/Modules/Contacts/Details/Views/RoomTableViewCell.xib +++ b/Riot/Modules/Contacts/Details/Views/RoomTableViewCell.xib @@ -1,13 +1,11 @@ - + - - - + @@ -57,14 +55,15 @@ + - + diff --git a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.h b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.h index dd318b9260..9881e328bf 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.h +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.h @@ -18,18 +18,6 @@ #import "DeviceTableViewCell.h" -@interface RoomMemberDetailsViewController : MXKRoomMemberDetailsViewController +@interface RoomMemberDetailsViewController : MXKRoomMemberDetailsViewController -@property (weak, nonatomic) IBOutlet UIView *roomMemberAvatarHeaderBackground; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *roomMemberAvatarHeaderBackgroundHeightConstraint; - -@property (weak, nonatomic) IBOutlet UIView *memberHeaderView; -@property (weak, nonatomic) IBOutlet UIView *roomMemberAvatarMask; -@property (weak, nonatomic) IBOutlet UILabel *roomMemberNameLabel; -@property (weak, nonatomic) IBOutlet UIView *roomMemberNameLabelMask; - -@property (weak, nonatomic) IBOutlet UILabel *roomMemberStatusLabel; - -@property (weak, nonatomic) IBOutlet UIImageView *bottomImageView; @end - diff --git a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m index 89662ec224..7322ce27a5 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m @@ -33,10 +33,13 @@ #define TABLEVIEW_SECTION_HEADER_HEIGHT 28 #define TABLEVIEW_SECTION_HEADER_HEIGHT_WHEN_HIDDEN 0.01f -@interface RoomMemberDetailsViewController () +@interface RoomMemberDetailsViewController () { RoomMemberTitleView* memberTitleView; + NSInteger securityIndex; + NSMutableArray *securityActionsArray; + /** List of the admin actions on this member. */ @@ -78,6 +81,26 @@ List of the direct chats (room ids) with this member. */ BOOL isStatusBarHidden; } + +@property (weak, nonatomic) IBOutlet UIView *roomMemberAvatarHeaderBackground; +@property (weak, nonatomic) IBOutlet NSLayoutConstraint *roomMemberAvatarHeaderBackgroundHeightConstraint; + +@property (weak, nonatomic) IBOutlet UIView *memberHeaderView; +@property (weak, nonatomic) IBOutlet UIView *roomMemberAvatarMask; +@property (weak, nonatomic) IBOutlet UIImageView *roomMemberAvatarBadgeImageView; + +@property (weak, nonatomic) IBOutlet UILabel *roomMemberNameLabel; +@property (weak, nonatomic) IBOutlet UIView *roomMemberNameLabelMask; + +@property (weak, nonatomic) IBOutlet UILabel *roomMemberStatusLabel; + +@property (weak, nonatomic) IBOutlet UIImageView *bottomImageView; + + +@property(nonatomic) UserEncryptionTrustLevel encryptionTrustLevel; + +@property(nonatomic, strong) UserVerificationCoordinatorBridgePresenter *userVerificationCoordinatorBridgePresenter; + @end @implementation RoomMemberDetailsViewController @@ -105,6 +128,7 @@ - (void)finalizeInit // Setup `MXKViewControllerHandling` properties self.enableBarTintColorStatusChange = NO; self.rageShakeManager = [RageShakeManager sharedManager]; + self.encryptionTrustLevel = UserEncryptionTrustLevelUnknown; adminActionsArray = [[NSMutableArray alloc] init]; otherActionsArray = [[NSMutableArray alloc] init]; @@ -195,10 +219,15 @@ - (void)viewDidLoad [self.tableView registerClass:TableViewCellWithButton.class forCellReuseIdentifier:[TableViewCellWithButton defaultReuseIdentifier]]; [self.tableView registerClass:RoomTableViewCell.class forCellReuseIdentifier:[RoomTableViewCell defaultReuseIdentifier]]; [self.tableView registerClass:DeviceTableViewCell.class forCellReuseIdentifier:[DeviceTableViewCell defaultReuseIdentifier]]; + [self.tableView registerClass:MXKTableViewCell.class forCellReuseIdentifier:[MXKTableViewCell defaultReuseIdentifier]]; // Hide line separators of empty cells self.tableView.tableFooterView = [[UIView alloc] init]; + // Enable self sizing cells + self.tableView.rowHeight = UITableViewAutomaticDimension; + self.tableView.estimatedRowHeight = 50; + // Observe UIApplicationWillChangeStatusBarOrientationNotification to hide/show bubbles bg. UIApplicationWillChangeStatusBarOrientationNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillChangeStatusBarOrientationNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { @@ -262,6 +291,8 @@ - (void)viewWillAppear:(BOOL)animated // Handle here the bottom image visibility UIInterfaceOrientation screenOrientation = [[UIApplication sharedApplication] statusBarOrientation]; self.bottomImageView.hidden = (screenOrientation == UIInterfaceOrientationLandscapeLeft || screenOrientation == UIInterfaceOrientationLandscapeRight); + + [self refreshUserEncryptionTrustLevel]; } - (void)viewWillDisappear:(BOOL)animated @@ -382,14 +413,18 @@ - (void)updateMemberInfo NSString* presenceText; - if (self.mxRoomMember.userId) + NSString *userId = self.mxRoomMember.userId; + + if (userId) { - MXUser *user = [self.mxRoom.mxSession userWithUserId:self.mxRoomMember.userId]; + MXUser *user = [self.mxRoom.mxSession userWithUserId:userId]; presenceText = [Tools presenceText:user]; } self.roomMemberStatusLabel.text = presenceText; + self.roomMemberAvatarBadgeImageView.image = self.userEncryptionBadgeImage; + // Retrieve the existing direct chats [directChatsArray removeAllObjects]; NSArray *directRoomIds = self.mainSession.directRooms[self.mxRoomMember.userId]; @@ -403,37 +438,132 @@ - (void)updateMemberInfo } // Retrieve member's devices - NSString *userId = self.mxRoomMember.userId; __weak typeof(self) weakSelf = self; + + if (!RiotSettings.shared.enableCrossSigning) + { + [self.mxRoom.mxSession.crypto downloadKeys:@[userId] forceDownload:NO success:^(MXUsersDevicesMap *usersDevicesInfoMap, NSDictionary *crossSigningKeysMap) { + + if (weakSelf) + { + // Restore the status bar + typeof(self) self = weakSelf; + self->devicesArray = usersDevicesInfoMap.map[userId].allValues; + // Reload the full table to take into account a potential change on a device status. + [super updateMemberInfo]; + } + + } failure:^(NSError *error) { + + NSLog(@"[RoomMemberDetailsVC] Crypto failed to download device info for user: %@", userId); + if (weakSelf) + { + // Restore the status bar + typeof(self) self = weakSelf; + // Notify the end user + NSString *myUserId = self.mainSession.myUser.userId; + [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; + } + + }]; + } + } + + // Complete data update and reload table view + [super updateMemberInfo]; +} - [self.mxRoom.mxSession.crypto downloadKeys:@[userId] forceDownload:NO success:^(MXUsersDevicesMap *usersDevicesInfoMap, NSDictionary *crossSigningKeysMap) { - - if (weakSelf) +- (void)refreshUserEncryptionTrustLevel +{ + NSString *userId = self.mxRoomMember.userId; + + if (!userId) + { + return; + } + + if (self.mxRoom.summary.isEncrypted && self.mxRoom.mxSession.crypto) + { + [self.mxRoom.mxSession.crypto trustLevelSummaryForUserIds:@[userId] success:^(MXUsersTrustLevelSummary *usersTrustLevelSummary) { + + double trustedDevicesPercentage = usersTrustLevelSummary.trustedDevicesProgress.fractionCompleted; + + UserEncryptionTrustLevel userEncryptionTrustLevel; + + if (trustedDevicesPercentage >= 1.0) { - // Restore the status bar - typeof(self) self = weakSelf; - self->devicesArray = usersDevicesInfoMap.map[userId].allValues; - // Reload the full table to take into account a potential change on a device status. - [super updateMemberInfo]; + userEncryptionTrustLevel = UserEncryptionTrustLevelTrusted; } - - } failure:^(NSError *error) { - - NSLog(@"[RoomMemberDetailsVC] Crypto failed to download device info for user: %@", userId); - if (weakSelf) + else if (trustedDevicesPercentage == 0.0) + { + userEncryptionTrustLevel = UserEncryptionTrustLevelNormal; + } + else { - // Restore the status bar - typeof(self) self = weakSelf; - // Notify the end user - NSString *myUserId = self.mainSession.myUser.userId; - [[NSNotificationCenter defaultCenter] postNotificationName:kMXKErrorNotification object:error userInfo:myUserId ? @{kMXKErrorUserIdKey: myUserId} : nil]; + userEncryptionTrustLevel = UserEncryptionTrustLevelWarning; } + self.encryptionTrustLevel = userEncryptionTrustLevel; + [self updateMemberInfo]; + + } failure:^(NSError *error) { + NSLog(@"[RoomMemberDetailsViewController] Fails to retrieve trust level summary with error: %@", error); }]; } + else + { + self.encryptionTrustLevel = UserEncryptionTrustLevelNone; + [self updateMemberInfo]; + } +} + +- (UIImage*)userEncryptionBadgeImage +{ + NSString *encryptionIconName; + UIImage *encryptionIcon; + + UserEncryptionTrustLevel userEncryptionTrustLevel = self.encryptionTrustLevel; + + switch (userEncryptionTrustLevel) { + case RoomEncryptionTrustLevelWarning: + encryptionIconName = @"encryption_warning"; + break; + case RoomEncryptionTrustLevelNormal: + encryptionIconName = @"encryption_normal"; + break; + case RoomEncryptionTrustLevelTrusted: + encryptionIconName = @"encryption_trusted"; + break; + default: + break; + } - // Complete data update and reload table view - [super updateMemberInfo]; + if (encryptionIconName) + { + encryptionIcon = [UIImage imageNamed:encryptionIconName]; + } + + return encryptionIcon; +} + +- (BOOL)isRoomMemberCurrentUser +{ + return [self.mxRoomMember.userId isEqualToString:self.mainSession.myUser.userId]; +} + +- (void)startUserVerification +{ + [[AppDelegate theDelegate] presentUserVerificationForRoomMember:self.mxRoomMember session:self.mainSession]; +} + +- (void)presentUserVerification +{ + UserVerificationCoordinatorBridgePresenter *userVerificationCoordinatorBridgePresenter = [[UserVerificationCoordinatorBridgePresenter alloc] initWithPresenter:self + session:self.mxRoom.mxSession + userId:self.mxRoomMember.userId + userDisplayName:self.mxRoomMember.displayname]; + [userVerificationCoordinatorBridgePresenter start]; + self.userVerificationCoordinatorBridgePresenter = userVerificationCoordinatorBridgePresenter; } #pragma mark - Hide/Show navigation bar border @@ -484,7 +614,7 @@ - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView [otherActionsArray removeAllObjects]; // Consider the case of the user himself - if ([self.mxRoomMember.userId isEqualToString:self.mainSession.myUser.userId]) + if (self.isRoomMemberCurrentUser) { isOneself = YES; @@ -618,7 +748,12 @@ - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView } } - adminToolsIndex = otherActionsIndex = directChatsIndex = devicesIndex = -1; + securityIndex = adminToolsIndex = otherActionsIndex = directChatsIndex = devicesIndex = -1; + + if (RiotSettings.shared.enableCrossSigning) + { + securityIndex = sectionCount++; + } if (otherActionsArray.count) { @@ -644,7 +779,23 @@ - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - if (section == adminToolsIndex) + if (section == securityIndex) + { + NSInteger numberOfRows; + + switch (self.encryptionTrustLevel) { + case UserEncryptionTrustLevelUnknown: + case UserEncryptionTrustLevelNone: + numberOfRows = 1; + break; + default: + numberOfRows = 2; + break; + } + + return numberOfRows; + } + else if (section == adminToolsIndex) { return adminActionsArray.count; } @@ -666,10 +817,18 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger - (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { - if (section == adminToolsIndex) + if (section == securityIndex) + { + return @"SECURITY"; + } + else if (section == adminToolsIndex) { return NSLocalizedStringFromTable(@"room_participants_action_section_admin_tools", @"Vector", nil); } + else if (RiotSettings.shared.enableCrossSigning && section == otherActionsIndex) + { + return @"OPTIONS"; + } else if (section == directChatsIndex) { return NSLocalizedStringFromTable(@"room_participants_action_section_direct_chats", @"Vector", nil); @@ -741,7 +900,78 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N { UITableViewCell *cell; - if (indexPath.section == adminToolsIndex || indexPath.section == otherActionsIndex) + if (indexPath.section == securityIndex) + { + if (indexPath.row == [self tableView:self.tableView numberOfRowsInSection:indexPath.section] - 1) + { + MXKTableViewCell *encryptionInfoCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCell defaultReuseIdentifier] forIndexPath:indexPath]; + + NSMutableString *encryptionInformation = [NSMutableString new]; + + switch (self.encryptionTrustLevel) { + case UserEncryptionTrustLevelUnknown: + [encryptionInformation appendString:@"Loading"]; + break; + case UserEncryptionTrustLevelNone: + [encryptionInformation appendString:@"Messages in this room are not end-to-end encrypted."]; + break; + default: + [encryptionInformation appendString:@"Messages in this room are end-to-end encrypted.\n\nYour messages are secured with locks and only you and the recipient have the unique keys to unlock them."]; + break; + } + + [encryptionInformation appendString:@"\n"]; + + encryptionInfoCell.textLabel.backgroundColor = [UIColor clearColor]; + encryptionInfoCell.textLabel.numberOfLines = 0; + encryptionInfoCell.textLabel.text = encryptionInformation; + encryptionInfoCell.textLabel.font = [UIFont systemFontOfSize:14.0]; + encryptionInfoCell.textLabel.textColor = ThemeService.shared.theme.headerTextPrimaryColor; + + encryptionInfoCell.selectionStyle = UITableViewCellSelectionStyleNone; + encryptionInfoCell.accessoryType = UITableViewCellAccessoryNone; + encryptionInfoCell.contentView.backgroundColor = ThemeService.shared.theme.headerBackgroundColor; + encryptionInfoCell.backgroundColor = ThemeService.shared.theme.headerBackgroundColor; + + cell = encryptionInfoCell; + } + else + { + MXKTableViewCell *securityStatusCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCell defaultReuseIdentifier] forIndexPath:indexPath]; + + NSString *statusText; + + switch (self.encryptionTrustLevel) { + case UserEncryptionTrustLevelTrusted: + statusText = @"Verified"; + break; + case UserEncryptionTrustLevelNormal: + statusText = @"Verify"; + break; + case UserEncryptionTrustLevelWarning: + statusText = @"Warning"; + break; + default: + statusText = @"Loading"; + break; + } + + securityStatusCell.imageView.image = self.userEncryptionBadgeImage; + + securityStatusCell.textLabel.numberOfLines = 1; + securityStatusCell.textLabel.font = [UIFont systemFontOfSize:16.0]; + securityStatusCell.textLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + securityStatusCell.textLabel.text = statusText; + + securityStatusCell.backgroundColor = ThemeService.shared.theme.backgroundColor; + securityStatusCell.contentView.backgroundColor = [UIColor clearColor]; + securityStatusCell.selectionStyle = UITableViewCellSelectionStyleNone; + securityStatusCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + + cell = securityStatusCell; + } + } + else if (indexPath.section == adminToolsIndex || indexPath.section == otherActionsIndex) { TableViewCellWithButton *cellWithButton = [tableView dequeueReusableCellWithIdentifier:[TableViewCellWithButton defaultReuseIdentifier] forIndexPath:indexPath]; @@ -852,26 +1082,9 @@ - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)ce } } -- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath -{ - if (indexPath.section == directChatsIndex) - { - return [RoomTableViewCell cellHeight]; - } - else if (indexPath.section == devicesIndex) - { - if (indexPath.row < devicesArray.count) - { - return [DeviceTableViewCell cellHeightWithDeviceInfo:devicesArray[indexPath.row] andCellWidth:self.tableView.frame.size.width]; - } - } - - return TABLEVIEW_ROW_CELL_HEIGHT; -} - - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { - if (section == otherActionsIndex) + if (!RiotSettings.shared.enableCrossSigning && section == otherActionsIndex) { return TABLEVIEW_SECTION_HEADER_HEIGHT_WHEN_HIDDEN; } @@ -881,7 +1094,18 @@ - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSIntege - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(nonnull NSIndexPath *)indexPath { - if (indexPath.section == directChatsIndex) + if (indexPath.section == securityIndex) + { + if (self.encryptionTrustLevel == UserEncryptionTrustLevelNormal) + { + [self startUserVerification]; + } + else + { + [self presentUserVerification]; + } + } + else if (indexPath.section == directChatsIndex) { if (indexPath.row < directChatsArray.count) { diff --git a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.xib b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.xib index b03f8eec42..b723cb4f45 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.xib +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.xib @@ -1,12 +1,11 @@ - + - - + @@ -15,6 +14,7 @@ + @@ -45,7 +45,7 @@ - + @@ -53,11 +53,21 @@ + + + + + + + + + + From 76bcb0256eff3923e583e78efca1df62c60f52e5 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 30 Jan 2020 18:05:55 +0100 Subject: [PATCH 138/282] Update pbxproj --- Riot.xcodeproj/project.pbxproj | 150 +++++++++++++++++++++++++++++++ Riot/Generated/Storyboards.swift | 15 ++++ 2 files changed, 165 insertions(+) diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index efad11f477..eb5802c590 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -179,6 +179,14 @@ B125FE21231D5E1D00B72806 /* SettingsDiscoveryViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B125FE20231D5E1D00B72806 /* SettingsDiscoveryViewAction.swift */; }; B125FE23231D5E4300B72806 /* SettingsDiscoveryViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B125FE22231D5E4300B72806 /* SettingsDiscoveryViewState.swift */; }; B12C56EF2396CB5E00FAC6DE /* RoomMessageURLParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12C56EE2396CB5E00FAC6DE /* RoomMessageURLParser.swift */; }; + B12D79FB23E2462200FACEDC /* UserVerificationStartCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12D79F323E2462000FACEDC /* UserVerificationStartCoordinator.swift */; }; + B12D79FC23E2462200FACEDC /* UserVerificationStartViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B12D79F423E2462100FACEDC /* UserVerificationStartViewController.storyboard */; }; + B12D79FD23E2462200FACEDC /* UserVerificationStartViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12D79F523E2462100FACEDC /* UserVerificationStartViewModelType.swift */; }; + B12D79FE23E2462200FACEDC /* UserVerificationStartViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12D79F623E2462100FACEDC /* UserVerificationStartViewController.swift */; }; + B12D79FF23E2462200FACEDC /* UserVerificationStartViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12D79F723E2462100FACEDC /* UserVerificationStartViewState.swift */; }; + B12D7A0023E2462200FACEDC /* UserVerificationStartCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12D79F823E2462200FACEDC /* UserVerificationStartCoordinatorType.swift */; }; + B12D7A0123E2462200FACEDC /* UserVerificationStartViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12D79F923E2462200FACEDC /* UserVerificationStartViewModel.swift */; }; + B12D7A0223E2462200FACEDC /* UserVerificationStartViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12D79FA23E2462200FACEDC /* UserVerificationStartViewAction.swift */; }; B139C21B21FE5B9200BB68EC /* KeyBackupRecoverFromPassphraseViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B139C21A21FE5B9100BB68EC /* KeyBackupRecoverFromPassphraseViewModel.swift */; }; B139C21D21FE5BF500BB68EC /* KeyBackupRecoverFromPassphraseViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B139C21C21FE5BF500BB68EC /* KeyBackupRecoverFromPassphraseViewModelType.swift */; }; B139C21F21FE5D6600BB68EC /* KeyBackupRecoverFromPassphraseViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B139C21E21FE5D6600BB68EC /* KeyBackupRecoverFromPassphraseViewAction.swift */; }; @@ -532,6 +540,27 @@ B1BD71BC238E8F9600BA92E2 /* WidgetPermissionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BD71BA238E8F9600BA92E2 /* WidgetPermissionViewController.swift */; }; B1BD71BF238EA56700BA92E2 /* WidgetPermissionViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B1BD71BE238EA56700BA92E2 /* WidgetPermissionViewController.storyboard */; }; B1BD71C1238EA92100BA92E2 /* WidgetPermissionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BD71C0238EA92000BA92E2 /* WidgetPermissionViewModel.swift */; }; + B1BEE71423DF2ACF0003A4CB /* UserVerificationCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE71123DF2ACF0003A4CB /* UserVerificationCoordinatorType.swift */; }; + B1BEE71523DF2ACF0003A4CB /* UserVerificationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE71223DF2ACF0003A4CB /* UserVerificationCoordinator.swift */; }; + B1BEE71623DF2ACF0003A4CB /* UserVerificationCoordinatorBridgePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE71323DF2ACF0003A4CB /* UserVerificationCoordinatorBridgePresenter.swift */; }; + B1BEE72A23DF38B20003A4CB /* UserVerificationSessionStatusCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE72823DF38B10003A4CB /* UserVerificationSessionStatusCell.swift */; }; + B1BEE72B23DF38B20003A4CB /* UserVerificationSessionStatusCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1BEE72923DF38B20003A4CB /* UserVerificationSessionStatusCell.xib */; }; + B1BEE73423DF44A60003A4CB /* UserVerificationSessionsStatusViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE72C23DF44A20003A4CB /* UserVerificationSessionsStatusViewModelType.swift */; }; + B1BEE73523DF44A60003A4CB /* UserVerificationSessionsStatusViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE72D23DF44A30003A4CB /* UserVerificationSessionsStatusViewModel.swift */; }; + B1BEE73623DF44A60003A4CB /* UserVerificationSessionsStatusCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE72E23DF44A30003A4CB /* UserVerificationSessionsStatusCoordinatorType.swift */; }; + B1BEE73723DF44A60003A4CB /* UserVerificationSessionsStatusViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE72F23DF44A40003A4CB /* UserVerificationSessionsStatusViewState.swift */; }; + B1BEE73823DF44A60003A4CB /* UserVerificationSessionsStatusViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE73023DF44A40003A4CB /* UserVerificationSessionsStatusViewAction.swift */; }; + B1BEE73923DF44A60003A4CB /* UserVerificationSessionsStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE73123DF44A50003A4CB /* UserVerificationSessionsStatusViewController.swift */; }; + B1BEE73A23DF44A60003A4CB /* UserVerificationSessionsStatusViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B1BEE73223DF44A50003A4CB /* UserVerificationSessionsStatusViewController.storyboard */; }; + B1BEE73B23DF44A60003A4CB /* UserVerificationSessionsStatusCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE73323DF44A60003A4CB /* UserVerificationSessionsStatusCoordinator.swift */; }; + B1BEE74623E093260003A4CB /* UserVerificationSessionStatusViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE73E23E093220003A4CB /* UserVerificationSessionStatusViewState.swift */; }; + B1BEE74723E093260003A4CB /* UserVerificationSessionStatusViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE73F23E093230003A4CB /* UserVerificationSessionStatusViewModelType.swift */; }; + B1BEE74823E093260003A4CB /* UserVerificationSessionStatusViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE74023E093230003A4CB /* UserVerificationSessionStatusViewModel.swift */; }; + B1BEE74923E093260003A4CB /* UserVerificationSessionStatusViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B1BEE74123E093230003A4CB /* UserVerificationSessionStatusViewController.storyboard */; }; + B1BEE74A23E093260003A4CB /* UserVerificationSessionStatusCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE74223E093240003A4CB /* UserVerificationSessionStatusCoordinatorType.swift */; }; + B1BEE74B23E093260003A4CB /* UserVerificationSessionStatusViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE74323E093250003A4CB /* UserVerificationSessionStatusViewAction.swift */; }; + B1BEE74C23E093260003A4CB /* UserVerificationSessionStatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE74423E093250003A4CB /* UserVerificationSessionStatusViewController.swift */; }; + B1BEE74D23E093260003A4CB /* UserVerificationSessionStatusCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1BEE74523E093260003A4CB /* UserVerificationSessionStatusCoordinator.swift */; }; B1C335CD22F1C1320021BA8D /* CameraPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C335CC22F1C1320021BA8D /* CameraPresenter.swift */; }; B1C3360122F1ED600021BA8D /* MediaPickerCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C335FE22F1ED5F0021BA8D /* MediaPickerCoordinatorType.swift */; }; B1C3360222F1ED600021BA8D /* MediaPickerCoordinatorBridgePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C335FF22F1ED5F0021BA8D /* MediaPickerCoordinatorBridgePresenter.swift */; }; @@ -878,6 +907,14 @@ B125FE20231D5E1D00B72806 /* SettingsDiscoveryViewAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryViewAction.swift; sourceTree = ""; }; B125FE22231D5E4300B72806 /* SettingsDiscoveryViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDiscoveryViewState.swift; sourceTree = ""; }; B12C56EE2396CB5E00FAC6DE /* RoomMessageURLParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMessageURLParser.swift; sourceTree = ""; }; + B12D79F323E2462000FACEDC /* UserVerificationStartCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationStartCoordinator.swift; sourceTree = ""; }; + B12D79F423E2462100FACEDC /* UserVerificationStartViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = UserVerificationStartViewController.storyboard; sourceTree = ""; }; + B12D79F523E2462100FACEDC /* UserVerificationStartViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationStartViewModelType.swift; sourceTree = ""; }; + B12D79F623E2462100FACEDC /* UserVerificationStartViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationStartViewController.swift; sourceTree = ""; }; + B12D79F723E2462100FACEDC /* UserVerificationStartViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationStartViewState.swift; sourceTree = ""; }; + B12D79F823E2462200FACEDC /* UserVerificationStartCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationStartCoordinatorType.swift; sourceTree = ""; }; + B12D79F923E2462200FACEDC /* UserVerificationStartViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationStartViewModel.swift; sourceTree = ""; }; + B12D79FA23E2462200FACEDC /* UserVerificationStartViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationStartViewAction.swift; sourceTree = ""; }; B139C21A21FE5B9100BB68EC /* KeyBackupRecoverFromPassphraseViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewModel.swift; sourceTree = ""; }; B139C21C21FE5BF500BB68EC /* KeyBackupRecoverFromPassphraseViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewModelType.swift; sourceTree = ""; }; B139C21E21FE5D6600BB68EC /* KeyBackupRecoverFromPassphraseViewAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewAction.swift; sourceTree = ""; }; @@ -1416,6 +1453,28 @@ B1BD71BA238E8F9600BA92E2 /* WidgetPermissionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetPermissionViewController.swift; sourceTree = ""; }; B1BD71BE238EA56700BA92E2 /* WidgetPermissionViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = WidgetPermissionViewController.storyboard; sourceTree = ""; }; B1BD71C0238EA92000BA92E2 /* WidgetPermissionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetPermissionViewModel.swift; sourceTree = ""; }; + B1BEE71123DF2ACF0003A4CB /* UserVerificationCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationCoordinatorType.swift; sourceTree = ""; }; + B1BEE71223DF2ACF0003A4CB /* UserVerificationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationCoordinator.swift; sourceTree = ""; }; + B1BEE71323DF2ACF0003A4CB /* UserVerificationCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationCoordinatorBridgePresenter.swift; sourceTree = ""; }; + B1BEE72823DF38B10003A4CB /* UserVerificationSessionStatusCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionStatusCell.swift; sourceTree = ""; }; + B1BEE72923DF38B20003A4CB /* UserVerificationSessionStatusCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = UserVerificationSessionStatusCell.xib; sourceTree = ""; }; + B1BEE72C23DF44A20003A4CB /* UserVerificationSessionsStatusViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionsStatusViewModelType.swift; sourceTree = ""; }; + B1BEE72D23DF44A30003A4CB /* UserVerificationSessionsStatusViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionsStatusViewModel.swift; sourceTree = ""; }; + B1BEE72E23DF44A30003A4CB /* UserVerificationSessionsStatusCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionsStatusCoordinatorType.swift; sourceTree = ""; }; + B1BEE72F23DF44A40003A4CB /* UserVerificationSessionsStatusViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionsStatusViewState.swift; sourceTree = ""; }; + B1BEE73023DF44A40003A4CB /* UserVerificationSessionsStatusViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionsStatusViewAction.swift; sourceTree = ""; }; + B1BEE73123DF44A50003A4CB /* UserVerificationSessionsStatusViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionsStatusViewController.swift; sourceTree = ""; }; + B1BEE73223DF44A50003A4CB /* UserVerificationSessionsStatusViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = UserVerificationSessionsStatusViewController.storyboard; sourceTree = ""; }; + B1BEE73323DF44A60003A4CB /* UserVerificationSessionsStatusCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionsStatusCoordinator.swift; sourceTree = ""; }; + B1BEE73C23E070300003A4CB /* UserEncryptionTrustLevel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UserEncryptionTrustLevel.h; sourceTree = ""; }; + B1BEE73E23E093220003A4CB /* UserVerificationSessionStatusViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionStatusViewState.swift; sourceTree = ""; }; + B1BEE73F23E093230003A4CB /* UserVerificationSessionStatusViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionStatusViewModelType.swift; sourceTree = ""; }; + B1BEE74023E093230003A4CB /* UserVerificationSessionStatusViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionStatusViewModel.swift; sourceTree = ""; }; + B1BEE74123E093230003A4CB /* UserVerificationSessionStatusViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = UserVerificationSessionStatusViewController.storyboard; sourceTree = ""; }; + B1BEE74223E093240003A4CB /* UserVerificationSessionStatusCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionStatusCoordinatorType.swift; sourceTree = ""; }; + B1BEE74323E093250003A4CB /* UserVerificationSessionStatusViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionStatusViewAction.swift; sourceTree = ""; }; + B1BEE74423E093250003A4CB /* UserVerificationSessionStatusViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionStatusViewController.swift; sourceTree = ""; }; + B1BEE74523E093260003A4CB /* UserVerificationSessionStatusCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationSessionStatusCoordinator.swift; sourceTree = ""; }; B1C335CC22F1C1320021BA8D /* CameraPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPresenter.swift; sourceTree = ""; }; B1C335FE22F1ED5F0021BA8D /* MediaPickerCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaPickerCoordinatorType.swift; sourceTree = ""; }; B1C335FF22F1ED5F0021BA8D /* MediaPickerCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaPickerCoordinatorBridgePresenter.swift; sourceTree = ""; }; @@ -2124,6 +2183,21 @@ path = RoomMessageLinkParser; sourceTree = ""; }; + B12D79F223E2426800FACEDC /* Start */ = { + isa = PBXGroup; + children = ( + B12D79F323E2462000FACEDC /* UserVerificationStartCoordinator.swift */, + B12D79F823E2462200FACEDC /* UserVerificationStartCoordinatorType.swift */, + B12D79FA23E2462200FACEDC /* UserVerificationStartViewAction.swift */, + B12D79F423E2462100FACEDC /* UserVerificationStartViewController.storyboard */, + B12D79F623E2462100FACEDC /* UserVerificationStartViewController.swift */, + B12D79F923E2462200FACEDC /* UserVerificationStartViewModel.swift */, + B12D79F523E2462100FACEDC /* UserVerificationStartViewModelType.swift */, + B12D79F723E2462100FACEDC /* UserVerificationStartViewState.swift */, + ); + path = Start; + sourceTree = ""; + }; B14F142522144F6400FA0595 /* RecoveryKey */ = { isa = PBXGroup; children = ( @@ -2513,6 +2587,7 @@ B1B5567620EE6C4C00210D55 /* Modules */ = { isa = PBXGroup; children = ( + B1BEE71023DF28CA0003A4CB /* UserVerification */, B1A12C64239AB74500AA2B86 /* CrossSigning */, B1A6C10523881ECB002882FD /* SlidingModal */, 32DB556722FDADE50016329E /* ServiceTerms */, @@ -2712,6 +2787,7 @@ B1B556A620EE6C4C00210D55 /* Detail */ = { isa = PBXGroup; children = ( + B1BEE73C23E070300003A4CB /* UserEncryptionTrustLevel.h */, B1B556A720EE6C4C00210D55 /* RoomMemberDetailsViewController.h */, B1B556A820EE6C4C00210D55 /* RoomMemberDetailsViewController.m */, B1B556A920EE6C4C00210D55 /* RoomMemberDetailsViewController.xib */, @@ -3668,6 +3744,51 @@ path = ReactionHistory; sourceTree = ""; }; + B1BEE71023DF28CA0003A4CB /* UserVerification */ = { + isa = PBXGroup; + children = ( + B1BEE71223DF2ACF0003A4CB /* UserVerificationCoordinator.swift */, + B1BEE71123DF2ACF0003A4CB /* UserVerificationCoordinatorType.swift */, + B1BEE71323DF2ACF0003A4CB /* UserVerificationCoordinatorBridgePresenter.swift */, + B12D79F223E2426800FACEDC /* Start */, + B1BEE71723DF2B8A0003A4CB /* SessionsStatus */, + B1BEE73D23E08AC30003A4CB /* SessionStatus */, + ); + path = UserVerification; + sourceTree = ""; + }; + B1BEE71723DF2B8A0003A4CB /* SessionsStatus */ = { + isa = PBXGroup; + children = ( + B1BEE73323DF44A60003A4CB /* UserVerificationSessionsStatusCoordinator.swift */, + B1BEE72E23DF44A30003A4CB /* UserVerificationSessionsStatusCoordinatorType.swift */, + B1BEE73023DF44A40003A4CB /* UserVerificationSessionsStatusViewAction.swift */, + B1BEE73223DF44A50003A4CB /* UserVerificationSessionsStatusViewController.storyboard */, + B1BEE73123DF44A50003A4CB /* UserVerificationSessionsStatusViewController.swift */, + B1BEE72D23DF44A30003A4CB /* UserVerificationSessionsStatusViewModel.swift */, + B1BEE72C23DF44A20003A4CB /* UserVerificationSessionsStatusViewModelType.swift */, + B1BEE72F23DF44A40003A4CB /* UserVerificationSessionsStatusViewState.swift */, + B1BEE72823DF38B10003A4CB /* UserVerificationSessionStatusCell.swift */, + B1BEE72923DF38B20003A4CB /* UserVerificationSessionStatusCell.xib */, + ); + path = SessionsStatus; + sourceTree = ""; + }; + B1BEE73D23E08AC30003A4CB /* SessionStatus */ = { + isa = PBXGroup; + children = ( + B1BEE74523E093260003A4CB /* UserVerificationSessionStatusCoordinator.swift */, + B1BEE74223E093240003A4CB /* UserVerificationSessionStatusCoordinatorType.swift */, + B1BEE74323E093250003A4CB /* UserVerificationSessionStatusViewAction.swift */, + B1BEE74123E093230003A4CB /* UserVerificationSessionStatusViewController.storyboard */, + B1BEE74423E093250003A4CB /* UserVerificationSessionStatusViewController.swift */, + B1BEE74023E093230003A4CB /* UserVerificationSessionStatusViewModel.swift */, + B1BEE73F23E093230003A4CB /* UserVerificationSessionStatusViewModelType.swift */, + B1BEE73E23E093220003A4CB /* UserVerificationSessionStatusViewState.swift */, + ); + path = SessionStatus; + sourceTree = ""; + }; B1C3361A22F328AE0021BA8D /* Camera */ = { isa = PBXGroup; children = ( @@ -4165,6 +4286,7 @@ B1A6C113238BD245002882FD /* SlidingModalContainerView.xib in Resources */, B1B5594420EF7BD000210D55 /* TableViewCellWithCollectionView.xib in Resources */, B1B558D520EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib in Resources */, + B12D79FC23E2462200FACEDC /* UserVerificationStartViewController.storyboard in Resources */, B1B5590020EF768F00210D55 /* RoomOutgoingTextMsgWithoutSenderNameBubbleCell.xib in Resources */, B1B5597020EFA85D00210D55 /* EncryptionInfoView.xib in Resources */, B1B558BC20EF768F00210D55 /* RoomMembershipBubbleCell.xib in Resources */, @@ -4187,6 +4309,7 @@ B1B9DEEA22EB34EF0065E677 /* ReactionHistoryViewController.storyboard in Resources */, B1B9194C2118984300FE25B5 /* RoomPredecessorBubbleCell.xib in Resources */, B1C562E9228C7CF20037F12A /* ContextualMenuItemView.xib in Resources */, + B1BEE73A23DF44A60003A4CB /* UserVerificationSessionsStatusViewController.storyboard in Resources */, B1B5572120EE6C4D00210D55 /* ContactsTableViewController.xib in Resources */, B1B5593A20EF7BAC00210D55 /* TableViewCellWithLabelAndLargeTextView.xib in Resources */, B1B558D820EF768F00210D55 /* RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.xib in Resources */, @@ -4256,10 +4379,12 @@ B1B558D120EF768F00210D55 /* RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.xib in Resources */, B1B558FE20EF768F00210D55 /* RoomMembershipExpandedWithPaginationTitleBubbleCell.xib in Resources */, B1B5581D20EF625800210D55 /* RoomAvatarTitleView.xib in Resources */, + B1BEE74923E093260003A4CB /* UserVerificationSessionStatusViewController.storyboard in Resources */, B1B5590B20EF768F00210D55 /* RoomMembershipExpandedBubbleCell.xib in Resources */, B1B558E720EF768F00210D55 /* RoomIncomingTextMsgWithoutSenderInfoBubbleCell.xib in Resources */, B1B558DC20EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.xib in Resources */, B1B5572E20EE6C4D00210D55 /* ReadReceiptsViewController.xib in Resources */, + B1BEE72B23DF38B20003A4CB /* UserVerificationSessionStatusCell.xib in Resources */, B1B5574220EE6C4D00210D55 /* RecentsViewController.xib in Resources */, B1B5571C20EE6C4D00210D55 /* DeactivateAccountViewController.storyboard in Resources */, B1B5596520EF9E9B00210D55 /* RoomTableViewCell.xib in Resources */, @@ -4549,6 +4674,7 @@ 3232AB4C2256558300AD6A5C /* TemplateScreenCoordinator.swift in Sources */, B1B5575920EE6C4D00210D55 /* HomeMessagesSearchViewController.m in Sources */, B1B558DE20EF768F00210D55 /* RoomIncomingAttachmentBubbleCell.m in Sources */, + B1BEE74D23E093260003A4CB /* UserVerificationSessionStatusCoordinator.swift in Sources */, B1B5574820EE6C4D00210D55 /* PeopleViewController.m in Sources */, B1B5598720EFC3E000210D55 /* Widget.m in Sources */, B1BD71C1238EA92100BA92E2 /* WidgetPermissionViewModel.swift in Sources */, @@ -4556,6 +4682,7 @@ B1CE9F062216FB09000FAE6A /* EncryptionKeysExportPresenter.swift in Sources */, 3232ABAA225730E100AD6A5C /* DeviceVerificationCoordinatorBridgePresenter.swift in Sources */, B1B5574420EE6C4D00210D55 /* CallViewController.m in Sources */, + B12D7A0023E2462200FACEDC /* UserVerificationStartCoordinatorType.swift in Sources */, B1B5572220EE6C4D00210D55 /* RoomSettingsViewController.m in Sources */, B1B5577320EE702800210D55 /* JitsiViewController.m in Sources */, B169331620F3CAFC00746532 /* PublicRoomsDirectoryDataSource.m in Sources */, @@ -4564,6 +4691,7 @@ 32242F1221E8FBA900725742 /* ThemeService.m in Sources */, B1B558E820EF768F00210D55 /* RoomIncomingAttachmentWithPaginationTitleBubbleCell.m in Sources */, B1B558F320EF768F00210D55 /* RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.m in Sources */, + B12D79FE23E2462200FACEDC /* UserVerificationStartViewController.swift in Sources */, B1B557BD20EF5B4500210D55 /* KeyboardGrowingTextView.m in Sources */, B1A68593229E807A00D6C09A /* RoomBubbleCellLayout.swift in Sources */, B1B558F420EF768F00210D55 /* RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */, @@ -4577,6 +4705,7 @@ B1DCC63F22E9A3AE00625807 /* EmojiItem+EmojiMart.swift in Sources */, B1DCC61C22E5E17100625807 /* EmojiPickerViewAction.swift in Sources */, B1098BDF21ECE09F000DDA48 /* Strings.swift in Sources */, + B1BEE71523DF2ACF0003A4CB /* UserVerificationCoordinator.swift in Sources */, B1B558C420EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */, 3232ABC022594C0900AD6A5C /* VerifyEmojiCollectionViewCell.swift in Sources */, B1963B2E228F1C4900CBA17F /* BubbleReactionViewData.swift in Sources */, @@ -4605,6 +4734,7 @@ B1C45A8C232A8C2600165425 /* SettingsIdentityServerViewAction.swift in Sources */, 32A6001E22C661100042C1D9 /* EditHistoryCoordinatorBridgePresenter.swift in Sources */, B1B5574A20EE6C4D00210D55 /* MediaPickerViewController.m in Sources */, + B1BEE74623E093260003A4CB /* UserVerificationSessionStatusViewState.swift in Sources */, B1B5598520EFC3E000210D55 /* RageShakeManager.m in Sources */, B1A6C111238BD236002882FD /* SlidingModalContainerView.swift in Sources */, B1DCC62D22E61EAF00625807 /* EmojiPickerViewCell.swift in Sources */, @@ -4630,6 +4760,7 @@ B1B5593B20EF7BAC00210D55 /* TableViewCellWithCheckBoxAndLabel.m in Sources */, B1B5581A20EF625800210D55 /* ExpandedRoomTitleView.m in Sources */, B1107EC82200B0720038014B /* KeyBackupRecoverSuccessViewController.swift in Sources */, + B1BEE74C23E093260003A4CB /* UserVerificationSessionStatusViewController.swift in Sources */, B1B9DEEB22EB34EF0065E677 /* ReactionHistoryViewModel.swift in Sources */, B1C543B023A2871300DCA1FA /* KeyVerificationBaseBubbleCell.swift in Sources */, B1963B2F228F1C4900CBA17F /* BubbleReactionViewCell.swift in Sources */, @@ -4702,7 +4833,9 @@ B139C21D21FE5BF500BB68EC /* KeyBackupRecoverFromPassphraseViewModelType.swift in Sources */, B157FA9F23264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinator.swift in Sources */, B1C45A8B232A8C2600165425 /* SettingsIdentityServerViewModel.swift in Sources */, + B12D7A0123E2462200FACEDC /* UserVerificationStartViewModel.swift in Sources */, B12C56EF2396CB5E00FAC6DE /* RoomMessageURLParser.swift in Sources */, + B1BEE73623DF44A60003A4CB /* UserVerificationSessionsStatusCoordinatorType.swift in Sources */, B1C45A86232A8C2600165425 /* SettingsIdentityServerViewModelType.swift in Sources */, F083BE031E7009ED00A9B29C /* EventFormatter.m in Sources */, B157FAA623264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewController.swift in Sources */, @@ -4713,8 +4846,10 @@ 3232AB4F2256558300AD6A5C /* TemplateScreenViewController.swift in Sources */, B1B558FC20EF768F00210D55 /* RoomIncomingTextMsgWithPaginationTitleBubbleCell.m in Sources */, B1B5572920EE6C4D00210D55 /* RoomFilesViewController.m in Sources */, + B1BEE74B23E093260003A4CB /* UserVerificationSessionStatusViewAction.swift in Sources */, 3232ABBA2257BE6500AD6A5C /* DeviceVerificationVerifyViewModel.swift in Sources */, B1098C1021ED07E4000DDA48 /* Presentable.swift in Sources */, + B1BEE73923DF44A60003A4CB /* UserVerificationSessionsStatusViewController.swift in Sources */, B1B558E020EF768F00210D55 /* RoomOutgoingTextMsgBubbleCell.m in Sources */, B1C562E3228C7C8D0037F12A /* RoomContextualMenuPresenter.swift in Sources */, B1B5593C20EF7BAC00210D55 /* TableViewCellWithCheckBoxes.m in Sources */, @@ -4722,10 +4857,12 @@ B1CA3A2721EF6914000D1D89 /* UIViewController.swift in Sources */, 322C110822BBC6F80043FEAC /* WidgetManagerConfig.swift in Sources */, F0D2ADA11F6AA5FD00A7097D /* MXRoomSummary+Riot.m in Sources */, + B1BEE71423DF2ACF0003A4CB /* UserVerificationCoordinatorType.swift in Sources */, B1B5596F20EFA85D00210D55 /* EncryptionInfoView.m in Sources */, B1B5573820EE6C4D00210D55 /* GroupParticipantsViewController.m in Sources */, 3232ABBB2257BE6500AD6A5C /* DeviceVerificationVerifyViewState.swift in Sources */, 3232ABAB225730E100AD6A5C /* DeviceVerificationCoordinator.swift in Sources */, + B1BEE73B23DF44A60003A4CB /* UserVerificationSessionsStatusCoordinator.swift in Sources */, B1B5583E20EF6E7F00210D55 /* GroupRoomTableViewCell.m in Sources */, B14F143522144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewController.swift in Sources */, B1DCC61E22E5E17100625807 /* EmojiPickerViewModel.swift in Sources */, @@ -4733,6 +4870,7 @@ 32863A5C2384074C00D07C4A /* RiotSettingAllowedWidgets.swift in Sources */, B1B9DEDA22E9B7350065E677 /* SerializationService.swift in Sources */, B1B5572520EE6C4D00210D55 /* RoomMessagesSearchViewController.m in Sources */, + B12D79FD23E2462200FACEDC /* UserVerificationStartViewModelType.swift in Sources */, B1C543AE23A286A000DCA1FA /* KeyVerificationRequestStatusBubbleCell.swift in Sources */, B139C22121FE5D9D00BB68EC /* KeyBackupRecoverFromPassphraseViewState.swift in Sources */, B1B5579120EF568D00210D55 /* GroupInviteTableViewCell.m in Sources */, @@ -4744,6 +4882,7 @@ B1DCC63722E8541700625807 /* EmojiStore.swift in Sources */, 3232ABA6225730E100AD6A5C /* DeviceVerificationStartViewController.swift in Sources */, B16932EA20F3C39000746532 /* UnifiedSearchRecentsDataSource.m in Sources */, + B1BEE72A23DF38B20003A4CB /* UserVerificationSessionStatusCell.swift in Sources */, B1C45A8A232A8C2600165425 /* SettingsIdentityServerCoordinatorBridgePresenter.swift in Sources */, B1B557DE20EF5FBB00210D55 /* FilesSearchTableViewCell.m in Sources */, B1B5574020EE6C4D00210D55 /* SegmentedViewController.m in Sources */, @@ -4766,6 +4905,7 @@ 32A6001D22C661100042C1D9 /* EditHistoryCoordinatorType.swift in Sources */, F083BDFA1E7009ED00A9B29C /* RoomPreviewData.m in Sources */, B1B557B420EF5AEF00210D55 /* EventDetailsView.m in Sources */, + B1BEE73823DF44A60003A4CB /* UserVerificationSessionsStatusViewAction.swift in Sources */, B1B5577E20EE84BF00210D55 /* IncomingCallView.m in Sources */, B1DCC62822E60CE300625807 /* EmojiCategory.swift in Sources */, B14084CC23BF9DE90010F692 /* KeyVerificationConclusionWithPaginationTitleBubbleCell.swift in Sources */, @@ -4780,6 +4920,7 @@ B1D250D82118AA0A000F4E93 /* RoomPredecessorBubbleCell.m in Sources */, B1B5577120EE702800210D55 /* StickerPickerViewController.m in Sources */, 32FDC1CD2386CD390084717A /* RiotSettingIntegrationProvisioning.swift in Sources */, + B1BEE73523DF44A60003A4CB /* UserVerificationSessionsStatusViewModel.swift in Sources */, B104C2942203773C00D9F496 /* KeyBackupBannerPreferences.swift in Sources */, B1B5572020EE6C4D00210D55 /* ContactsTableViewController.m in Sources */, B1B5581920EF625800210D55 /* RoomTitleView.m in Sources */, @@ -4794,8 +4935,10 @@ B1B5597520EFB02A00210D55 /* InviteRecentTableViewCell.m in Sources */, B1B5571E20EE6C4D00210D55 /* ContactDetailsViewController.m in Sources */, B1798302211B13B3001FD722 /* OnBoardingManager.swift in Sources */, + B1BEE74823E093260003A4CB /* UserVerificationSessionStatusViewModel.swift in Sources */, B1A6C10723881EF2002882FD /* SlidingModalPresenter.swift in Sources */, B1B5573520EE6C4D00210D55 /* GroupDetailsViewController.m in Sources */, + B12D7A0223E2462200FACEDC /* UserVerificationStartViewAction.swift in Sources */, B10B3B5B2201DD740072C76B /* KeyBackupBannerCell.swift in Sources */, B1DCC61A22E5E17100625807 /* EmojiPickerViewController.swift in Sources */, B1963B32228F1C6B00CBA17F /* BubbleReactionsViewModelType.swift in Sources */, @@ -4808,6 +4951,7 @@ 329E746722CD02EA006F9797 /* BubbleReactionActionViewCell.swift in Sources */, B1098BFB21ECFE65000DDA48 /* KeyBackupSetupCoordinatorType.swift in Sources */, B1098BF721ECFE65000DDA48 /* PasswordStrength.swift in Sources */, + B1BEE73423DF44A60003A4CB /* UserVerificationSessionsStatusViewModelType.swift in Sources */, 324A2052225FC571004FE8B0 /* DeviceVerificationIncomingViewAction.swift in Sources */, B105778D2213051E00334B1E /* KeyBackupSetupSuccessFromRecoveryKeyViewController.swift in Sources */, B1B557D820EF5EA900210D55 /* RoomActivitiesView.m in Sources */, @@ -4824,6 +4968,7 @@ B1D211E622C194A200D939BD /* ReactionsMenuViewState.swift in Sources */, B17982FF2119FED2001FD722 /* GDPRConsentViewController.swift in Sources */, B1098BE121ECE09F000DDA48 /* Images.swift in Sources */, + B1BEE74A23E093260003A4CB /* UserVerificationSessionStatusCoordinatorType.swift in Sources */, 3232ABA4225730E100AD6A5C /* DeviceVerificationStartViewAction.swift in Sources */, B1B5575A20EE6C4D00210D55 /* UnifiedSearchViewController.m in Sources */, 3232AB492256558300AD6A5C /* FlowTemplateCoordinatorBridgePresenter.swift in Sources */, @@ -4837,6 +4982,7 @@ B125FE23231D5E4300B72806 /* SettingsDiscoveryViewState.swift in Sources */, B1B5593820EF7BAC00210D55 /* TableViewCellWithLabelAndLargeTextView.m in Sources */, B1DCC62222E60BE000625807 /* EmojiPickerItemViewData.swift in Sources */, + B1BEE74723E093260003A4CB /* UserVerificationSessionStatusViewModelType.swift in Sources */, 3232AB502256558300AD6A5C /* TemplateScreenViewState.swift in Sources */, B1C335CD22F1C1320021BA8D /* CameraPresenter.swift in Sources */, B1B558C820EF768F00210D55 /* RoomIncomingEncryptedAttachmentBubbleCell.m in Sources */, @@ -4873,6 +5019,7 @@ B1C3360322F1ED600021BA8D /* MediaPickerCoordinator.swift in Sources */, B1E5368D21FB7245001F3AFF /* KeyBackupRecoverFromPassphraseViewController.swift in Sources */, B1963B3822933BC800CBA17F /* AutosizedCollectionView.swift in Sources */, + B12D79FB23E2462200FACEDC /* UserVerificationStartCoordinator.swift in Sources */, B169330320F3C98900746532 /* RoomBubbleCellData.m in Sources */, B1B557CC20EF5D8000210D55 /* DirectoryServerTableViewCell.m in Sources */, B1963B2B228F1C4900CBA17F /* BubbleReactionsView.swift in Sources */, @@ -4911,6 +5058,7 @@ 3232AB522256558300AD6A5C /* TemplateScreenViewModel.swift in Sources */, B1B5575B20EE6C4D00210D55 /* HomeFilesSearchViewController.m in Sources */, B139C22521FF01C100BB68EC /* KeyBackupRecoverFromPassphraseCoordinator.swift in Sources */, + B1BEE71623DF2ACF0003A4CB /* UserVerificationCoordinatorBridgePresenter.swift in Sources */, B1098BFD21ECFE65000DDA48 /* PasswordStrengthManager.swift in Sources */, B1B558F520EF768F00210D55 /* RoomOutgoingTextMsgWithPaginationTitleBubbleCell.m in Sources */, 3232AB482256558300AD6A5C /* FlowTemplateCoordinatorType.swift in Sources */, @@ -4920,10 +5068,12 @@ B125FE1B231D5BF200B72806 /* SettingsDiscoveryTableViewSection.swift in Sources */, 32242F0921E8B05F00725742 /* UIColor.swift in Sources */, B16932E720F3C37100746532 /* HomeMessagesSearchDataSource.m in Sources */, + B12D79FF23E2462200FACEDC /* UserVerificationStartViewState.swift in Sources */, B1B558CE20EF768F00210D55 /* RoomOutgoingEncryptedAttachmentBubbleCell.m in Sources */, B1B5577D20EE84BF00210D55 /* CircleButton.m in Sources */, 32BF995521FA2AB700698084 /* SettingsKeyBackupViewAction.swift in Sources */, B109D6F1222D8C400061B6D9 /* UIApplication.swift in Sources */, + B1BEE73723DF44A60003A4CB /* UserVerificationSessionsStatusViewState.swift in Sources */, B108932823ABEE6800802670 /* BubbleCellReadReceiptsDisplayable.swift in Sources */, B1B558FF20EF768F00210D55 /* RoomIncomingTextMsgBubbleCell.m in Sources */, B1098C0021ECFE65000DDA48 /* KeyBackupSetupPassphraseViewController.swift in Sources */, diff --git a/Riot/Generated/Storyboards.swift b/Riot/Generated/Storyboards.swift index 7ec5424ed2..66ef12a4b3 100644 --- a/Riot/Generated/Storyboards.swift +++ b/Riot/Generated/Storyboards.swift @@ -117,6 +117,21 @@ internal enum StoryboardScene { internal static let initialScene = InitialSceneType(storyboard: TemplateScreenViewController.self) } + internal enum UserVerificationSessionStatusViewController: StoryboardType { + internal static let storyboardName = "UserVerificationSessionStatusViewController" + + internal static let initialScene = InitialSceneType(storyboard: UserVerificationSessionStatusViewController.self) + } + internal enum UserVerificationSessionsStatusViewController: StoryboardType { + internal static let storyboardName = "UserVerificationSessionsStatusViewController" + + internal static let initialScene = InitialSceneType(storyboard: UserVerificationSessionsStatusViewController.self) + } + internal enum UserVerificationStartViewController: StoryboardType { + internal static let storyboardName = "UserVerificationStartViewController" + + internal static let initialScene = InitialSceneType(storyboard: UserVerificationStartViewController.self) + } internal enum WidgetPermissionViewController: StoryboardType { internal static let storyboardName = "WidgetPermissionViewController" From 178d6e5b974b6d505169dfecb4c3128059ad5b72 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 30 Jan 2020 18:13:49 +0100 Subject: [PATCH 139/282] E2E: Do not warn anymore for unknown devices # 2959 --- CHANGES.rst | 1 + Riot/AppDelegate.m | 3 +++ Riot/Modules/Settings/SettingsViewController.m | 2 ++ 3 files changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 2c9f973b5a..7e311f7ab4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,7 @@ Changes in 0.11.0 (2020-xx-xx) =============================================== Improvements: + * E2E: Do not warn anymore for unknown devices * ON/OFF Cross-signing development in a Lab setting (#2855). * RoomVC: Update encryption decoration with shields (#2934, #2930, #2906). * Settings: Remove "End-to-End Encryption" from the LABS section (#2941). diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index ddde73e322..522c38bc19 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -2801,6 +2801,9 @@ - (void)initMatrixSessions break; } } + + // Do not warn for unknown devices if cross-signing is enabled + mxSession.crypto.warnOnUnknowDevices = !RiotSettings.shared.enableCrossSigning; } else if (mxSession.state == MXSessionStateClosed) { diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index a65477d660..dd5c792859 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -3056,6 +3056,8 @@ - (void)toggleLabsCrossSigning:(id)sender UISwitch *switchButton = (UISwitch*)sender; RiotSettings.shared.enableCrossSigning = switchButton.isOn; + + self.mainSession.crypto.warnOnUnknowDevices = !RiotSettings.shared.enableCrossSigning; } - (void)togglePinRoomsWithMissedNotif:(id)sender From 01612d735a0438359f6aa19eeb87658950b4312c Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 30 Jan 2020 20:57:51 +0100 Subject: [PATCH 140/282] Update Riot/AppDelegate.m Co-Authored-By: manuroe --- Riot/AppDelegate.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 35b5236cd1..6e7523bba0 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -4901,7 +4901,7 @@ - (BOOL)presentUserVerificationForRoomMember:(MXRoomMember*)roomMember session:( deviceVerificationCoordinatorBridgePresenter = [[DeviceVerificationCoordinatorBridgePresenter alloc] initWithSession:mxSession]; deviceVerificationCoordinatorBridgePresenter.delegate = self; - [deviceVerificationCoordinatorBridgePresenter presentFrom:presentingViewController roomMember:roomMember animated:mxSession]; + [deviceVerificationCoordinatorBridgePresenter presentFrom:presentingViewController roomMember:roomMember animated:YES]; presented = YES; } From d37e23edfc72e887eae9e12c1e967da9542a75ac Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 30 Jan 2020 20:58:12 +0100 Subject: [PATCH 141/282] Update Riot/Modules/UserVerification/Start/UserVerificationStartViewModelType.swift Co-Authored-By: manuroe --- .../Start/UserVerificationStartViewModelType.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/Modules/UserVerification/Start/UserVerificationStartViewModelType.swift b/Riot/Modules/UserVerification/Start/UserVerificationStartViewModelType.swift index f50a4ccb8d..ce8feb50a2 100644 --- a/Riot/Modules/UserVerification/Start/UserVerificationStartViewModelType.swift +++ b/Riot/Modules/UserVerification/Start/UserVerificationStartViewModelType.swift @@ -24,7 +24,7 @@ protocol UserVerificationStartViewModelViewDelegate: class { protocol UserVerificationStartViewModelCoordinatorDelegate: class { - func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, didCompleteWithOutgoingTransaction transaction: MXSASTransaction) + func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, didCompleteWithIncomingTransaction transaction: MXSASTransaction) func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, didTransactionCancelled transaction: MXSASTransaction) From 81466c5417d40388eeb7fe13444739fb3d89019b Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 30 Jan 2020 21:37:25 +0100 Subject: [PATCH 142/282] Update MXUsersTrustLevelSummary fetch. --- .../Modules/Room/DataSources/RoomDataSource.m | 7 +-- .../Detail/RoomMemberDetailsViewController.m | 46 +++++++++---------- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m index 3bd0d0165f..f74b646577 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -238,9 +238,10 @@ - (void)encryptionTrustLevelDidChangeRelatedToUserId:(NSString*)userId // If user belongs to the room refresh the trust level if (roomMember) - { - [self.room membersTrustLevelSummaryWithSuccess:^(MXUsersTrustLevelSummary *usersTrustLevelSummary) { - + { + [self.room membersTrustLevelSummaryWithForceDownload:NO + success:^(MXUsersTrustLevelSummary *usersTrustLevelSummary) { + RoomEncryptionTrustLevel roomEncryptionTrustLevel; double trustedDevicesPercentage = usersTrustLevelSummary.trustedDevicesProgress.fractionCompleted; diff --git a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m index 7322ce27a5..3c49b1ef07 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m @@ -484,31 +484,27 @@ - (void)refreshUserEncryptionTrustLevel if (self.mxRoom.summary.isEncrypted && self.mxRoom.mxSession.crypto) { - [self.mxRoom.mxSession.crypto trustLevelSummaryForUserIds:@[userId] success:^(MXUsersTrustLevelSummary *usersTrustLevelSummary) { - - double trustedDevicesPercentage = usersTrustLevelSummary.trustedDevicesProgress.fractionCompleted; - - UserEncryptionTrustLevel userEncryptionTrustLevel; - - if (trustedDevicesPercentage >= 1.0) - { - userEncryptionTrustLevel = UserEncryptionTrustLevelTrusted; - } - else if (trustedDevicesPercentage == 0.0) - { - userEncryptionTrustLevel = UserEncryptionTrustLevelNormal; - } - else - { - userEncryptionTrustLevel = UserEncryptionTrustLevelWarning; - } - - self.encryptionTrustLevel = userEncryptionTrustLevel; - [self updateMemberInfo]; - - } failure:^(NSError *error) { - NSLog(@"[RoomMemberDetailsViewController] Fails to retrieve trust level summary with error: %@", error); - }]; + MXUsersTrustLevelSummary *usersTrustLevelSummary = [self.mxRoom.mxSession.crypto trustLevelSummaryForUserIds:@[userId]]; + + double trustedDevicesPercentage = usersTrustLevelSummary.trustedDevicesProgress.fractionCompleted; + + UserEncryptionTrustLevel userEncryptionTrustLevel; + + if (trustedDevicesPercentage >= 1.0) + { + userEncryptionTrustLevel = UserEncryptionTrustLevelTrusted; + } + else if (trustedDevicesPercentage == 0.0) + { + userEncryptionTrustLevel = UserEncryptionTrustLevelNormal; + } + else + { + userEncryptionTrustLevel = UserEncryptionTrustLevelWarning; + } + + self.encryptionTrustLevel = userEncryptionTrustLevel; + [self updateMemberInfo]; } else { From c70452661a4ac1d8a93c88d4412f527d7a7345f1 Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 31 Jan 2020 14:59:54 +0100 Subject: [PATCH 143/282] Room decoration: Use shields instead of padlocks #2906 --- CHANGES.rst | 1 + Riot/AppDelegate.m | 1 + Riot/Categories/MXRoomSummary+Riot.h | 18 +++++ Riot/Categories/MXRoomSummary+Riot.m | 27 +++++++ .../Recents/Views/RecentTableViewCell.m | 38 +++++++++- .../Home/Views/RoomCollectionViewCell.m | 38 +++++++++- .../Modules/Room/DataSources/RoomDataSource.h | 10 +-- .../Modules/Room/DataSources/RoomDataSource.m | 76 +++---------------- .../Listing/Views/RecentRoomTableViewCell.m | 38 +++++++++- 9 files changed, 168 insertions(+), 79 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 2c9f973b5a..a1ec981a5a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,7 @@ Improvements: * ON/OFF Cross-signing development in a Lab setting (#2855). * RoomVC: Update encryption decoration with shields (#2934, #2930, #2906). * Settings: Remove "End-to-End Encryption" from the LABS section (#2941). + * Room decoration: Use shields instead of padlocks (#2906). Changes in 0.10.4 (2019-12-11) =============================================== diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index ddde73e322..779442d161 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -266,6 +266,7 @@ + (void)initialize // Set the App Group identifier. MXSDKOptions *sdkOptions = [MXSDKOptions sharedInstance]; sdkOptions.applicationGroupIdentifier = @"group.im.vector"; + sdkOptions.computeE2ERoomSummaryTrust = YES; // Redirect NSLogs to files only if we are not debugging if (!isatty(STDERR_FILENO)) diff --git a/Riot/Categories/MXRoomSummary+Riot.h b/Riot/Categories/MXRoomSummary+Riot.h index 804a6a6bec..c69617bf5f 100644 --- a/Riot/Categories/MXRoomSummary+Riot.h +++ b/Riot/Categories/MXRoomSummary+Riot.h @@ -16,6 +16,17 @@ #import +/** + RoomEncryptionTrustLevel represents the trust level in an encrypted room. + */ +typedef NS_ENUM(NSUInteger, RoomEncryptionTrustLevel) { + RoomEncryptionTrustLevelTrusted, + RoomEncryptionTrustLevelWarning, + RoomEncryptionTrustLevelNormal, + RoomEncryptionTrustLevelUnknown +}; + + /** Define a `MXRoomSummary` category at Riot level. */ @@ -32,4 +43,11 @@ */ - (void)setRoomAvatarImageIn:(MXKImageView*)mxkImageView; +/** + Get the trust level in the room. + + @return the trust level. + */ +- (RoomEncryptionTrustLevel)roomEncryptionTrustLevel; + @end diff --git a/Riot/Categories/MXRoomSummary+Riot.m b/Riot/Categories/MXRoomSummary+Riot.m index 0367f2436f..fb13dccc4a 100644 --- a/Riot/Categories/MXRoomSummary+Riot.m +++ b/Riot/Categories/MXRoomSummary+Riot.m @@ -47,4 +47,31 @@ - (void)setRoomAvatarImageIn:(MXKImageView*)mxkImageView mxkImageView.contentMode = UIViewContentModeScaleAspectFill; } +- (RoomEncryptionTrustLevel)roomEncryptionTrustLevel +{ + RoomEncryptionTrustLevel roomEncryptionTrustLevel = RoomEncryptionTrustLevelUnknown; + if (self.trust) + { + double trustedDevicesPercentage = self.trust.trustedDevicesProgress.fractionCompleted; + + if (trustedDevicesPercentage >= 1.0 + || self.trust.trustedDevicesProgress.totalUnitCount == 0) + { + roomEncryptionTrustLevel = RoomEncryptionTrustLevelTrusted; + } + else if (trustedDevicesPercentage == 0.0) + { + roomEncryptionTrustLevel = RoomEncryptionTrustLevelNormal; + } + else + { + roomEncryptionTrustLevel = RoomEncryptionTrustLevelWarning; + } + + roomEncryptionTrustLevel = roomEncryptionTrustLevel; + } + + return roomEncryptionTrustLevel; +} + @end diff --git a/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m b/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m index 1f7e1b443a..a33e0e5e0b 100644 --- a/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m +++ b/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m @@ -137,7 +137,15 @@ - (void)render:(MXKCellData *)cellData self.directRoomBorderView.hidden = !roomCellData.roomSummary.room.isDirect; - self.encryptedRoomIcon.hidden = !roomCellData.roomSummary.isEncrypted; + if (roomCellData.roomSummary.isEncrypted) + { + self.encryptedRoomIcon.hidden = NO; + self.encryptedRoomIcon.image = [self shieldImageForTrustLevel:roomCellData.roomSummary.roomEncryptionTrustLevel]; + } + else + { + self.encryptedRoomIcon.hidden = YES; + } [roomCellData.roomSummary setRoomAvatarImageIn:self.roomAvatar]; } @@ -153,4 +161,32 @@ + (CGFloat)heightForCellData:(MXKCellData *)cellData withMaximumWidth:(CGFloat)m return 74; } +- (UIImage*)shieldImageForTrustLevel:(RoomEncryptionTrustLevel)roomEncryptionTrustLevel +{ + UIImage *shieldImage; + + NSString *encryptionIconName; + switch (roomEncryptionTrustLevel) + { + case RoomEncryptionTrustLevelWarning: + encryptionIconName = @"encryption_warning"; + break; + case RoomEncryptionTrustLevelNormal: + encryptionIconName = @"encryption_normal"; + break; + case RoomEncryptionTrustLevelTrusted: + encryptionIconName = @"encryption_trusted"; + break; + case RoomEncryptionTrustLevelUnknown: + encryptionIconName = @"encryption_normal"; + break; + } + + if (encryptionIconName) + { + shieldImage = [UIImage imageNamed:encryptionIconName]; + } + return shieldImage; +} + @end diff --git a/Riot/Modules/Home/Views/RoomCollectionViewCell.m b/Riot/Modules/Home/Views/RoomCollectionViewCell.m index 5c6ed3aa35..8bd307ecf3 100644 --- a/Riot/Modules/Home/Views/RoomCollectionViewCell.m +++ b/Riot/Modules/Home/Views/RoomCollectionViewCell.m @@ -159,7 +159,15 @@ - (void)render:(MXKCellData *)cellData self.directRoomBorderView.hidden = !roomCellData.roomSummary.room.isDirect; - self.encryptedRoomIcon.hidden = !roomCellData.roomSummary.isEncrypted; + if (roomCellData.roomSummary.isEncrypted) + { + self.encryptedRoomIcon.hidden = NO; + self.encryptedRoomIcon.image = [self shieldImageForTrustLevel:roomCellData.roomSummary.roomEncryptionTrustLevel]; + } + else + { + self.encryptedRoomIcon.hidden = YES; + } [roomCellData.roomSummary setRoomAvatarImageIn:self.roomAvatar]; } @@ -207,5 +215,33 @@ - (NSString*)roomId return nil; } +- (UIImage*)shieldImageForTrustLevel:(RoomEncryptionTrustLevel)roomEncryptionTrustLevel +{ + UIImage *shieldImage; + + NSString *encryptionIconName; + switch (roomEncryptionTrustLevel) + { + case RoomEncryptionTrustLevelWarning: + encryptionIconName = @"encryption_warning"; + break; + case RoomEncryptionTrustLevelNormal: + encryptionIconName = @"encryption_normal"; + break; + case RoomEncryptionTrustLevelTrusted: + encryptionIconName = @"encryption_trusted"; + break; + case RoomEncryptionTrustLevelUnknown: + encryptionIconName = @"encryption_normal"; + break; + } + + if (encryptionIconName) + { + shieldImage = [UIImage imageNamed:encryptionIconName]; + } + return shieldImage; +} + @end diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.h b/Riot/Modules/Room/DataSources/RoomDataSource.h index ab09e2d2f5..37ec55cbf0 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.h +++ b/Riot/Modules/Room/DataSources/RoomDataSource.h @@ -19,15 +19,7 @@ #import "WidgetManager.h" -/** - RoomEncryptionTrustLevel represents the room members trust level in an encrypted room. - */ -typedef NS_ENUM(NSUInteger, RoomEncryptionTrustLevel) { - RoomEncryptionTrustLevelTrusted, - RoomEncryptionTrustLevelWarning, - RoomEncryptionTrustLevelNormal, - RoomEncryptionTrustLevelUnknown -}; +#import "MXRoomSummary+Riot.h" @protocol RoomDataSourceDelegate; diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m index 175cff69af..e38abb7617 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -112,7 +112,7 @@ - (void)finalizeInitialization NSLog(@"[MXKRoomDataSource] finalizeRoomDataSource: Cannot retrieve all room members"); }]; } - + if (self.room.summary.isEncrypted) { [self fetchEncryptionTrustedLevel]; @@ -193,84 +193,26 @@ - (void)setNeedsUpdateAdditionalContentHeightForCellData:(id= 1.0 - || usersTrustLevelSummary.trustedDevicesProgress.totalUnitCount == 0) - { - roomEncryptionTrustLevel = RoomEncryptionTrustLevelTrusted; - } - else if (trustedDevicesPercentage == 0.0) - { - roomEncryptionTrustLevel = RoomEncryptionTrustLevelNormal; - } - else - { - roomEncryptionTrustLevel = RoomEncryptionTrustLevelWarning; - } - - self.encryptionTrustLevel = roomEncryptionTrustLevel; - [self.roomDataSourceDelegate roomDataSource:self didUpdateEncryptionTrustLevel:roomEncryptionTrustLevel]; - - } failure:^(NSError *error) { - NSLog(@"[RoomDataSource] trustLevelDidChangeRelatedToUserId fails to retrieve room members trusted progress"); - }]; - } - - } failure:^(NSError *error) { - NSLog(@"[RoomDataSource] trustLevelDidChangeRelatedToUserId fails to retrieve room members"); - }]; -} #pragma mark - diff --git a/RiotShareExtension/Modules/Share/Listing/Views/RecentRoomTableViewCell.m b/RiotShareExtension/Modules/Share/Listing/Views/RecentRoomTableViewCell.m index 4796cc841c..48db291000 100644 --- a/RiotShareExtension/Modules/Share/Listing/Views/RecentRoomTableViewCell.m +++ b/RiotShareExtension/Modules/Share/Listing/Views/RecentRoomTableViewCell.m @@ -71,7 +71,15 @@ - (void)render:(MXKCellData *)cellData self.directRoomBorderView.hidden = !roomCellData.roomSummary.isDirect; - self.encryptedRoomIcon.hidden = !roomCellData.roomSummary.isEncrypted; + if (roomCellData.roomSummary.isEncrypted) + { + self.encryptedRoomIcon.hidden = NO; + self.encryptedRoomIcon.image = [self shieldImageForTrustLevel:roomCellData.roomSummary.roomEncryptionTrustLevel]; + } + else + { + self.encryptedRoomIcon.hidden = YES; + } } } @@ -80,4 +88,32 @@ + (CGFloat)cellHeight return 74; } +- (UIImage*)shieldImageForTrustLevel:(RoomEncryptionTrustLevel)roomEncryptionTrustLevel +{ + UIImage *shieldImage; + + NSString *encryptionIconName; + switch (roomEncryptionTrustLevel) + { + case RoomEncryptionTrustLevelWarning: + encryptionIconName = @"encryption_warning"; + break; + case RoomEncryptionTrustLevelNormal: + encryptionIconName = @"encryption_normal"; + break; + case RoomEncryptionTrustLevelTrusted: + encryptionIconName = @"encryption_trusted"; + break; + case RoomEncryptionTrustLevelUnknown: + encryptionIconName = @"encryption_normal"; + break; + } + + if (encryptionIconName) + { + shieldImage = [UIImage imageNamed:encryptionIconName]; + } + return shieldImage; +} + @end From b5e20087cb026211b8220a98a67498d3cc0cbcfe Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 31 Jan 2020 15:02:02 +0100 Subject: [PATCH 144/282] Add modules property on NavigationRouterType. --- Riot/Routers/NavigationRouter.swift | 5 ++++- Riot/Routers/NavigationRouterType.swift | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Riot/Routers/NavigationRouter.swift b/Riot/Routers/NavigationRouter.swift index a844bd34c6..d27ac204d9 100755 --- a/Riot/Routers/NavigationRouter.swift +++ b/Riot/Routers/NavigationRouter.swift @@ -24,10 +24,13 @@ final class NavigationRouter: NSObject, NavigationRouterType { // MARK: Private private var completions: [UIViewController : () -> Void] + private let navigationController: UINavigationController // MARK: Public - private let navigationController: UINavigationController + var modules: [Presentable] { + return navigationController.viewControllers + } // MARK: - Setup diff --git a/Riot/Routers/NavigationRouterType.swift b/Riot/Routers/NavigationRouterType.swift index edafecee2d..c32ea4e8e4 100755 --- a/Riot/Routers/NavigationRouterType.swift +++ b/Riot/Routers/NavigationRouterType.swift @@ -61,6 +61,9 @@ protocol NavigationRouterType: class, Presentable { /// /// - Parameter animated: Specify true to animate the transition. func popModule(animated: Bool) + + /// Returns the modules that are currently in the navigation stack + var modules: [Presentable] { get } } // `NavigationRouterType` default implementation From 5ad1596b8f9ee0de6b59d462a4d1eb9bb1d87432 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 31 Jan 2020 15:03:44 +0100 Subject: [PATCH 145/282] User verification: Add table view title for session list screen. --- ...VerificationSessionStatusViewController.swift | 1 + ...cationSessionsStatusViewController.storyboard | 16 +++++++++++++--- ...erificationSessionsStatusViewController.swift | 4 ++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewController.swift b/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewController.swift index 8d32b38681..748421b2ed 100644 --- a/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewController.swift +++ b/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewController.swift @@ -71,6 +71,7 @@ final class UserVerificationSessionStatusViewController: UIViewController { // Do any additional setup after loading the view. self.setupViews() + self.vc_removeBackTitle() self.activityPresenter = ActivityIndicatorPresenter() self.errorPresenter = MXKErrorAlertPresentation() diff --git a/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewController.storyboard b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewController.storyboard index 2d5c915f01..24f2aed271 100644 --- a/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewController.storyboard +++ b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewController.storyboard @@ -68,8 +68,14 @@ + - + @@ -81,10 +87,13 @@ + + - + + @@ -93,13 +102,14 @@ + - + diff --git a/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewController.swift b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewController.swift index f99a7e107c..21c298dce8 100644 --- a/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewController.swift +++ b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewController.swift @@ -34,6 +34,7 @@ final class UserVerificationSessionsStatusViewController: UIViewController { @IBOutlet private weak var titleLabel: UILabel! @IBOutlet private weak var closeButton: UIButton! @IBOutlet private weak var informationLabel: UILabel! + @IBOutlet private weak var sessionsTableViewTitle: UILabel! @IBOutlet private weak var tableView: UITableView! // MARK: Private @@ -62,6 +63,7 @@ final class UserVerificationSessionsStatusViewController: UIViewController { // Do any additional setup after loading the view. self.setupViews() + self.vc_removeBackTitle() self.activityIndicatorPresenter = ActivityIndicatorPresenter() self.errorPresenter = MXKErrorAlertPresentation() @@ -105,6 +107,7 @@ final class UserVerificationSessionsStatusViewController: UIViewController { self.closeButton.vc_setBackgroundColor(theme.headerTextSecondaryColor, for: .normal) self.titleLabel.textColor = theme.textPrimaryColor self.informationLabel.textColor = theme.textPrimaryColor + self.sessionsTableViewTitle.textColor = theme.textPrimaryColor } private func registerThemeServiceDidChangeThemeNotification() { @@ -120,6 +123,7 @@ final class UserVerificationSessionsStatusViewController: UIViewController { self.setupTableView() self.updateTitleViews() + self.sessionsTableViewTitle.text = "Sessions" self.informationLabel.text = "Messages with this user in this room are end-to-end encrypted and can’t be read by third parties." } From f008ae40f10baec99da39ea18b0b49bf14413d3c Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 31 Jan 2020 15:07:30 +0100 Subject: [PATCH 146/282] User verification: Update device verification flow to support device or user verification. --- Riot.xcodeproj/project.pbxproj | 6 +++- .../DeviceVerificationCoordinator.swift | 30 +++++++++++++---- .../KeyVerificationKind.swift | 21 ++++++++++++ ...erificationDataLoadingViewController.swift | 7 ++++ ...ceVerificationVerifiedViewController.swift | 32 +++++++++++++++---- .../DeviceVerificationVerifyCoordinator.swift | 4 +-- ...viceVerificationVerifyViewController.swift | 32 +++++++++++++------ .../DeviceVerificationVerifyViewModel.swift | 9 ++++-- ...eviceVerificationVerifyViewModelType.swift | 5 +-- .../Views/VerifyEmojiCollectionViewCell.swift | 3 +- 10 files changed, 118 insertions(+), 31 deletions(-) create mode 100644 Riot/Modules/DeviceVerification/KeyVerificationKind.swift diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index eb5802c590..dcfda59408 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -187,6 +187,7 @@ B12D7A0023E2462200FACEDC /* UserVerificationStartCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12D79F823E2462200FACEDC /* UserVerificationStartCoordinatorType.swift */; }; B12D7A0123E2462200FACEDC /* UserVerificationStartViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12D79F923E2462200FACEDC /* UserVerificationStartViewModel.swift */; }; B12D7A0223E2462200FACEDC /* UserVerificationStartViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12D79FA23E2462200FACEDC /* UserVerificationStartViewAction.swift */; }; + B12D7A0423E43DCC00FACEDC /* KeyVerificationKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12D7A0323E43DCC00FACEDC /* KeyVerificationKind.swift */; }; B139C21B21FE5B9200BB68EC /* KeyBackupRecoverFromPassphraseViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B139C21A21FE5B9100BB68EC /* KeyBackupRecoverFromPassphraseViewModel.swift */; }; B139C21D21FE5BF500BB68EC /* KeyBackupRecoverFromPassphraseViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B139C21C21FE5BF500BB68EC /* KeyBackupRecoverFromPassphraseViewModelType.swift */; }; B139C21F21FE5D6600BB68EC /* KeyBackupRecoverFromPassphraseViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B139C21E21FE5D6600BB68EC /* KeyBackupRecoverFromPassphraseViewAction.swift */; }; @@ -915,6 +916,7 @@ B12D79F823E2462200FACEDC /* UserVerificationStartCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationStartCoordinatorType.swift; sourceTree = ""; }; B12D79F923E2462200FACEDC /* UserVerificationStartViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationStartViewModel.swift; sourceTree = ""; }; B12D79FA23E2462200FACEDC /* UserVerificationStartViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserVerificationStartViewAction.swift; sourceTree = ""; }; + B12D7A0323E43DCC00FACEDC /* KeyVerificationKind.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyVerificationKind.swift; sourceTree = ""; }; B139C21A21FE5B9100BB68EC /* KeyBackupRecoverFromPassphraseViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewModel.swift; sourceTree = ""; }; B139C21C21FE5BF500BB68EC /* KeyBackupRecoverFromPassphraseViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewModelType.swift; sourceTree = ""; }; B139C21E21FE5D6600BB68EC /* KeyBackupRecoverFromPassphraseViewAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewAction.swift; sourceTree = ""; }; @@ -1749,8 +1751,9 @@ 324A2046225FC571004FE8B0 /* Incoming */, 32891D72226728EE00C82226 /* Loading */, 3232AB96225730E100AD6A5C /* Start */, - 32891D6D2264DF7B00C82226 /* Verified */, 3232ABAC2257BE6400AD6A5C /* Verify */, + 32891D6D2264DF7B00C82226 /* Verified */, + B12D7A0323E43DCC00FACEDC /* KeyVerificationKind.swift */, 3232AB95225730E100AD6A5C /* DeviceVerificationCoordinatorType.swift */, 3232AB9F225730E100AD6A5C /* DeviceVerificationCoordinatorBridgePresenter.swift */, 3232ABA0225730E100AD6A5C /* DeviceVerificationCoordinator.swift */, @@ -5082,6 +5085,7 @@ B1B9DEEE22EB34EF0065E677 /* ReactionHistoryViewAction.swift in Sources */, B1C543A4239E98E400DCA1FA /* KeyVerificationCellInnerContentView.swift in Sources */, 32B94DFA228EC26400716A26 /* ReactionsMenuButton.swift in Sources */, + B12D7A0423E43DCC00FACEDC /* KeyVerificationKind.swift in Sources */, B1B9DEEC22EB34EF0065E677 /* ReactionHistoryViewModelType.swift in Sources */, B157FAA523264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModel.swift in Sources */, B1C562E8228C7CF20037F12A /* ContextualMenuItemView.swift in Sources */, diff --git a/Riot/Modules/DeviceVerification/DeviceVerificationCoordinator.swift b/Riot/Modules/DeviceVerification/DeviceVerificationCoordinator.swift index b3ffdc89e0..476cf80cd1 100644 --- a/Riot/Modules/DeviceVerification/DeviceVerificationCoordinator.swift +++ b/Riot/Modules/DeviceVerification/DeviceVerificationCoordinator.swift @@ -28,12 +28,13 @@ final class DeviceVerificationCoordinator: DeviceVerificationCoordinatorType { private let navigationRouter: NavigationRouterType private let session: MXSession private let otherUserId: String - private let otherDeviceId: String - + private let otherDeviceId: String + private var incomingTransaction: MXIncomingSASTransaction? private var incomingKeyVerificationRequest: MXKeyVerificationRequest? - var roomMember: MXRoomMember? + private var verificationKind: KeyVerificationKind = .device + private var roomMember: MXRoomMember? // MARK: Public @@ -56,6 +57,13 @@ final class DeviceVerificationCoordinator: DeviceVerificationCoordinatorType { self.otherUserId = otherUserId self.otherDeviceId = otherDeviceId } + + init(navigationRouter: NavigationRouterType, session: MXSession, userId: String, otherDeviceId: String) { + self.navigationRouter = navigationRouter + self.session = session + self.otherUserId = userId + self.otherDeviceId = otherDeviceId + } /// Contrustor to manage an incoming SAS device verification transaction /// @@ -90,6 +98,7 @@ final class DeviceVerificationCoordinator: DeviceVerificationCoordinatorType { self.otherUserId = roomMember.userId self.otherDeviceId = "" self.roomMember = roomMember + self.verificationKind = .user } // MARK: - Public methods @@ -108,8 +117,15 @@ final class DeviceVerificationCoordinator: DeviceVerificationCoordinatorType { rootCoordinator.start() self.add(childCoordinator: rootCoordinator) - self.navigationRouter.setRootModule(rootCoordinator) { [weak self] in - self?.remove(childCoordinator: rootCoordinator) + + if self.navigationRouter.modules.isEmpty == false { + self.navigationRouter.push(rootCoordinator, animated: true, popCompletion: { [weak self] in + self?.remove(childCoordinator: rootCoordinator) + }) + } else { + self.navigationRouter.setRootModule(rootCoordinator) { [weak self] in + self?.remove(childCoordinator: rootCoordinator) + } } } @@ -166,7 +182,7 @@ final class DeviceVerificationCoordinator: DeviceVerificationCoordinatorType { } private func showVerify(transaction: MXSASTransaction, animated: Bool) { - let coordinator = DeviceVerificationVerifyCoordinator(session: self.session, transaction: transaction) + let coordinator = DeviceVerificationVerifyCoordinator(session: self.session, transaction: transaction, verificationKind: self.verificationKind) coordinator.delegate = self coordinator.start() @@ -177,7 +193,7 @@ final class DeviceVerificationCoordinator: DeviceVerificationCoordinatorType { } private func showVerified(animated: Bool) { - let viewController = DeviceVerificationVerifiedViewController.instantiate() + let viewController = DeviceVerificationVerifiedViewController.instantiate(with: self.verificationKind) viewController.delegate = self self.navigationRouter.setRootModule(viewController) } diff --git a/Riot/Modules/DeviceVerification/KeyVerificationKind.swift b/Riot/Modules/DeviceVerification/KeyVerificationKind.swift new file mode 100644 index 0000000000..d9d8f29fe7 --- /dev/null +++ b/Riot/Modules/DeviceVerification/KeyVerificationKind.swift @@ -0,0 +1,21 @@ +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import Foundation + +enum KeyVerificationKind { + case device + case user +} diff --git a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewController.swift b/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewController.swift index a21cc9f310..1d6a465ad0 100644 --- a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewController.swift +++ b/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewController.swift @@ -66,6 +66,13 @@ final class DeviceVerificationDataLoadingViewController: UIViewController { return self.theme.statusBarStyle } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + // Hide back button + self.navigationItem.setHidesBackButton(true, animated: animated) + } + // MARK: - Private private func update(theme: Theme) { diff --git a/Riot/Modules/DeviceVerification/Verified/DeviceVerificationVerifiedViewController.swift b/Riot/Modules/DeviceVerification/Verified/DeviceVerificationVerifiedViewController.swift index b9ebde0b58..a583410bd1 100644 --- a/Riot/Modules/DeviceVerification/Verified/DeviceVerificationVerifiedViewController.swift +++ b/Riot/Modules/DeviceVerification/Verified/DeviceVerificationVerifiedViewController.swift @@ -39,6 +39,7 @@ final class DeviceVerificationVerifiedViewController: UIViewController { // MARK: Private private var theme: Theme! + private var verificationKind: KeyVerificationKind = .user // MARK: Public @@ -46,9 +47,10 @@ final class DeviceVerificationVerifiedViewController: UIViewController { // MARK: - Setup - class func instantiate() -> DeviceVerificationVerifiedViewController { + class func instantiate(with verificationKind: KeyVerificationKind) -> DeviceVerificationVerifiedViewController { let viewController = StoryboardScene.DeviceVerificationVerifiedViewController.initialScene.instantiate() viewController.theme = ThemeService.shared().theme + viewController.verificationKind = verificationKind return viewController } @@ -59,7 +61,6 @@ final class DeviceVerificationVerifiedViewController: UIViewController { // Do any additional setup after loading the view. - self.title = VectorL10n.deviceVerificationTitle self.vc_removeBackTitle() self.setupViews() @@ -81,9 +82,28 @@ final class DeviceVerificationVerifiedViewController: UIViewController { // MARK: - Private private func setupViews() { - self.titleLabel.text = VectorL10n.deviceVerificationVerifiedTitle - self.description1Label.text = VectorL10n.deviceVerificationVerifiedDescription1 - self.description2Label.text = VectorL10n.deviceVerificationVerifiedDescription2 + let title: String + let bodyTitle: String + let descriptionTextPart1: String + let descriptionTextPart2: String + + switch self.verificationKind { + case .device: + title = VectorL10n.deviceVerificationTitle + bodyTitle = VectorL10n.deviceVerificationVerifiedTitle + descriptionTextPart1 = VectorL10n.deviceVerificationVerifiedDescription1 + descriptionTextPart2 = VectorL10n.deviceVerificationVerifiedDescription2 + case .user: + title = "Verify user" + bodyTitle = VectorL10n.deviceVerificationVerifiedTitle + descriptionTextPart1 = "You’ve successfully verified this user." + descriptionTextPart2 = "Messages with this user in this room are end-to-end encrypted and can’t be read by third parties." + } + + self.title = title + self.titleLabel.text = bodyTitle + self.description1Label.text = descriptionTextPart1 + self.description2Label.text = descriptionTextPart2 self.okButton.setTitle(VectorL10n.deviceVerificationVerifiedGotItButton, for: .normal) } @@ -103,7 +123,7 @@ final class DeviceVerificationVerifiedViewController: UIViewController { self.okButtonBackgroundView.backgroundColor = theme.backgroundColor theme.applyStyle(onButton: self.okButton) - } + } private func registerThemeServiceDidChangeThemeNotification() { NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) diff --git a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyCoordinator.swift b/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyCoordinator.swift index 4b5c835804..e3793c883d 100644 --- a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyCoordinator.swift +++ b/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyCoordinator.swift @@ -38,10 +38,10 @@ final class DeviceVerificationVerifyCoordinator: DeviceVerificationVerifyCoordin // MARK: - Setup - init(session: MXSession, transaction: MXSASTransaction) { + init(session: MXSession, transaction: MXSASTransaction, verificationKind: KeyVerificationKind) { self.session = session - let deviceVerificationVerifyViewModel = DeviceVerificationVerifyViewModel(session: self.session, transaction: transaction) + let deviceVerificationVerifyViewModel = DeviceVerificationVerifyViewModel(session: self.session, transaction: transaction, verificationKind: verificationKind) let deviceVerificationVerifyViewController = DeviceVerificationVerifyViewController.instantiate(with: deviceVerificationVerifyViewModel) self.deviceVerificationVerifyViewModel = deviceVerificationVerifyViewModel self.deviceVerificationVerifyViewController = deviceVerificationVerifyViewController diff --git a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewController.swift b/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewController.swift index 7869f9a319..69065a7514 100644 --- a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewController.swift +++ b/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewController.swift @@ -59,7 +59,6 @@ final class DeviceVerificationVerifyViewController: UIViewController { // Do any additional setup after loading the view. - self.title = VectorL10n.deviceVerificationTitle self.vc_removeBackTitle() self.setupViews() @@ -123,16 +122,33 @@ final class DeviceVerificationVerifyViewController: UIViewController { self.scrollView.keyboardDismissMode = .interactive - if viewModel.emojis != nil { + let isVerificationByEmoji = viewModel.emojis != nil + + if isVerificationByEmoji { self.decimalLabel.isHidden = true - self.titleLabel.text = VectorL10n.deviceVerificationVerifyTitleEmoji } else { self.emojisCollectionView.isHidden = true - self.titleLabel.text = VectorL10n.deviceVerificationVerifyTitleNumber self.decimalLabel.text = self.viewModel.decimal } + + let title: String + let instructionText: String + let adviceText: String + + switch viewModel.verificationKind { + case .device: + title = VectorL10n.deviceVerificationTitle + instructionText = isVerificationByEmoji ? VectorL10n.deviceVerificationVerifyTitleEmoji : VectorL10n.deviceVerificationVerifyTitleNumber + adviceText = VectorL10n.deviceVerificationSecurityAdvice + case .user: + title = "Verify user" + instructionText = isVerificationByEmoji ? "Verify this user by confirming the following unique emoji appears on their screen, in the same order." : "Verify this user by confirming the following numbers appear on their screen, in the same order." + adviceText = VectorL10n.deviceVerificationSecurityAdvice + } - self.informationLabel.text = VectorL10n.deviceVerificationSecurityAdvice + self.title = title + self.titleLabel.text = instructionText + self.informationLabel.text = adviceText self.waitingPartnerLabel.text = VectorL10n.deviceVerificationVerifyWaitPartner self.waitingPartnerLabel.isHidden = true @@ -223,10 +239,8 @@ extension DeviceVerificationVerifyViewController: UICollectionViewDataSource { } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - - guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "VerifyEmojiCollectionViewCell", for: indexPath) as? VerifyEmojiCollectionViewCell else { - return UICollectionViewCell() - } + + let cell = collectionView.dequeueReusableCell(for: indexPath, cellType: VerifyEmojiCollectionViewCell.self) guard let emoji = self.viewModel.emojis?[indexPath.row] else { return UICollectionViewCell() diff --git a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewModel.swift b/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewModel.swift index 2be7640a90..86c26a7c25 100644 --- a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewModel.swift +++ b/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewModel.swift @@ -31,16 +31,19 @@ final class DeviceVerificationVerifyViewModel: DeviceVerificationVerifyViewModel weak var viewDelegate: DeviceVerificationVerifyViewModelViewDelegate? weak var coordinatorDelegate: DeviceVerificationVerifyViewModelCoordinatorDelegate? - var emojis: [MXEmojiRepresentation]? - var decimal: String? + + let emojis: [MXEmojiRepresentation]? + let decimal: String? + let verificationKind: KeyVerificationKind // MARK: - Setup - init(session: MXSession, transaction: MXSASTransaction) { + init(session: MXSession, transaction: MXSASTransaction, verificationKind: KeyVerificationKind) { self.session = session self.transaction = transaction self.emojis = self.transaction.sasEmoji self.decimal = self.transaction.sasDecimal + self.verificationKind = verificationKind } deinit { diff --git a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewModelType.swift b/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewModelType.swift index 8ea8aa4498..9c5167f4fe 100644 --- a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewModelType.swift +++ b/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewModelType.swift @@ -35,6 +35,7 @@ protocol DeviceVerificationVerifyViewModelType { func process(viewAction: DeviceVerificationVerifyViewAction) - var emojis: [MXEmojiRepresentation]? { get set } - var decimal: String? { get set } + var emojis: [MXEmojiRepresentation]? { get } + var decimal: String? { get } + var verificationKind: KeyVerificationKind { get } } diff --git a/Riot/Modules/DeviceVerification/Verify/Views/VerifyEmojiCollectionViewCell.swift b/Riot/Modules/DeviceVerification/Verify/Views/VerifyEmojiCollectionViewCell.swift index 7d1780062d..e609f6b387 100644 --- a/Riot/Modules/DeviceVerification/Verify/Views/VerifyEmojiCollectionViewCell.swift +++ b/Riot/Modules/DeviceVerification/Verify/Views/VerifyEmojiCollectionViewCell.swift @@ -15,8 +15,9 @@ */ import UIKit +import Reusable -class VerifyEmojiCollectionViewCell: UICollectionViewCell, Themable { +class VerifyEmojiCollectionViewCell: UICollectionViewCell, Reusable, Themable { @IBOutlet weak var emoji: UILabel! @IBOutlet weak var name: UILabel! From 0695ed215b2f956213307cfbaafcb67499a4f75b Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 31 Jan 2020 15:09:19 +0100 Subject: [PATCH 147/282] User verification: User device verification flow when verify a session. --- .../UserVerificationStartCoordinator.swift | 2 +- .../UserVerificationStartViewModel.swift | 2 +- .../UserVerificationCoordinator.swift | 46 +++++-------------- 3 files changed, 13 insertions(+), 37 deletions(-) diff --git a/Riot/Modules/UserVerification/Start/UserVerificationStartCoordinator.swift b/Riot/Modules/UserVerification/Start/UserVerificationStartCoordinator.swift index 76910a825a..ffc2ce99fc 100644 --- a/Riot/Modules/UserVerification/Start/UserVerificationStartCoordinator.swift +++ b/Riot/Modules/UserVerification/Start/UserVerificationStartCoordinator.swift @@ -67,7 +67,7 @@ extension UserVerificationStartCoordinator: UserVerificationStartViewModelCoordi self.delegate?.userVerificationStartCoordinatorDidCancel(self) } - func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, didCompleteWithOutgoingTransaction transaction: MXSASTransaction) { + func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, didCompleteWithIncomingTransaction transaction: MXSASTransaction) { self.delegate?.userVerificationStartCoordinator(self, didCompleteWithOutgoingTransaction: transaction) } diff --git a/Riot/Modules/UserVerification/Start/UserVerificationStartViewModel.swift b/Riot/Modules/UserVerification/Start/UserVerificationStartViewModel.swift index 4ef9b3e0d5..a983675377 100644 --- a/Riot/Modules/UserVerification/Start/UserVerificationStartViewModel.swift +++ b/Riot/Modules/UserVerification/Start/UserVerificationStartViewModel.swift @@ -138,7 +138,7 @@ final class UserVerificationStartViewModel: UserVerificationStartViewModelType { switch transaction.state { case MXSASTransactionStateShowSAS: self.unregisterTransactionDidStateChangeNotification() - self.coordinatorDelegate?.userVerificationStartViewModel(self, didCompleteWithOutgoingTransaction: transaction) + self.coordinatorDelegate?.userVerificationStartViewModel(self, didCompleteWithIncomingTransaction: transaction) case MXSASTransactionStateCancelled: guard let reason = transaction.reasonCancelCode else { return diff --git a/Riot/Modules/UserVerification/UserVerificationCoordinator.swift b/Riot/Modules/UserVerification/UserVerificationCoordinator.swift index 5bda5e45f6..8fcd1c2392 100644 --- a/Riot/Modules/UserVerification/UserVerificationCoordinator.swift +++ b/Riot/Modules/UserVerification/UserVerificationCoordinator.swift @@ -90,35 +90,15 @@ final class UserVerificationCoordinator: NSObject, UserVerificationCoordinatorTy private func presentDeviceVerification(for deviceId: String) { - guard let deviceInfo = self.session.crypto.device(withDeviceId: deviceId, ofUser: self.userId) else { - NSLog("[UserVerificationCoordinator] Device not found") - return - } - - let encryptionInfoView: EncryptionInfoView = EncryptionInfoView(deviceInfo: deviceInfo, andMatrixSession: session) - encryptionInfoView.delegate = self - - // Skip the intro page - encryptionInfoView.displayLegacyVerificationScreen() - - // Display the legacy verification view in full screen - // TODO: Do not reuse the legacy EncryptionInfoView and create a screen from scratch - let viewController = UIViewController() - - viewController.view.backgroundColor = ThemeService.shared().theme.backgroundColor - viewController.view.addSubview(encryptionInfoView) - encryptionInfoView.translatesAutoresizingMaskIntoConstraints = false - - let superViewMargins = viewController.view.layoutMarginsGuide + let deviceVerificationCoordinator = DeviceVerificationCoordinator(navigationRouter: self.navigationRouter, session: self.session, userId: self.userId, otherDeviceId: deviceId) + deviceVerificationCoordinator.delegate = self + deviceVerificationCoordinator.start() - NSLayoutConstraint.activate([ - encryptionInfoView.topAnchor.constraint(equalTo: superViewMargins.topAnchor), - encryptionInfoView.leadingAnchor.constraint(equalTo: superViewMargins.leadingAnchor), - encryptionInfoView.trailingAnchor.constraint(equalTo: superViewMargins.trailingAnchor), - encryptionInfoView.bottomAnchor.constraint(equalTo: superViewMargins.bottomAnchor) - ]) + self.add(childCoordinator: deviceVerificationCoordinator) - self.navigationRouter.push(viewController, animated: true, popCompletion: nil) + self.navigationRouter.push(deviceVerificationCoordinator, animated: true, popCompletion: { + self.remove(childCoordinator: deviceVerificationCoordinator) + }) } } @@ -152,16 +132,12 @@ extension UserVerificationCoordinator: UserVerificationSessionStatusCoordinatorD } } -// MARK: - MXKEncryptionInfoViewDelegate -extension UserVerificationCoordinator: MXKEncryptionInfoViewDelegate { - func encryptionInfoView(_ encryptionInfoView: MXKEncryptionInfoView!, didDeviceInfoVerifiedChange deviceInfo: MXDeviceInfo!) { - - self.presenter.toPresentable().dismiss(animated: true) { - } - } +// MARK: - UserVerificationCoordinatorDelegate +extension UserVerificationCoordinator: DeviceVerificationCoordinatorDelegate { - func encryptionInfoViewDidClose(_ encryptionInfoView: MXKEncryptionInfoView!) { + func deviceVerificationCoordinatorDidComplete(_ coordinator: DeviceVerificationCoordinatorType, otherUserId: String, otherDeviceId: String) { self.presenter.toPresentable().dismiss(animated: true) { + self.remove(childCoordinator: coordinator) } } } From 8995c6dd6b4bd7bd6ac90c8a4025201e4ab599c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Fri, 31 Jan 2020 13:41:49 +0000 Subject: [PATCH 148/282] Translated using Weblate (French) Currently translated at 100.0% (877 of 877 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/fr/ --- Riot/Assets/fr.lproj/Vector.strings | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index 41cfb59eba..03c59fc0aa 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -958,3 +958,20 @@ "key_verification_tile_conclusion_warning_title" = "Connexion non approuvée"; "key_verification_incoming_request_incoming_alert_message" = "%@ veut vérifier"; "settings_labs_enable_cross_signing" = "Activer la vérification croisée pour vérifier par utilisateurs plutôt que par appareil (en développement)"; +"settings_security" = "SÉCURITÉ"; +// Security settings +"security_settings_title" = "Sécurité"; +"security_settings_crypto_sessions" = "MES SESSIONS"; +"security_settings_crypto_sessions_description" = "Faites confiance à des sessions pour leur accorder l’accès aux messages chiffrés de bout en bout. Si vous ne reconnaissez pas une session, modifiez votre mot de passe de connexion et réinitialisez votre mot de passe de messages utilisé pour la sauvegarde des messages."; +"security_settings_backup" = "SAUVEGARDE DES MESSAGES"; +"security_settings_advanced" = "AVANCÉ"; +"security_settings_blacklist_unverified_devices" = "Ne jamais envoyer de message aux sessions non fiables"; +"security_settings_blacklist_unverified_devices_description" = "Vérifier toutes les sessions d’un utilisateur pour les marquer comme fiables et leur envoyer des messages."; +"security_settings_export_keys_manually" = "Exporter les clés manuellement"; +// Manage session +"manage_session_title" = "Gérer la session"; +"manage_session_info" = "INFORMATIONS DE LA SESSION"; +"manage_session_name" = "Nom de l’appareil"; +"manage_session_trusted" = "Vous lui faites confiance"; +"manage_session_not_trusted" = "Vous ne lui faites pas confiance"; +"manage_session_sign_out" = "Se déconnecter de cet appareil"; From fb97b498c615dfe25831bede5514aab19871a36b Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 31 Jan 2020 16:20:28 +0100 Subject: [PATCH 149/282] Room decoration: Make shields bigger --- .../Recents/Views/RecentTableViewCell.xib | 18 +++++++++--------- .../Home/Views/RoomCollectionViewCell.xib | 17 ++++++++--------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/Riot/Modules/Common/Recents/Views/RecentTableViewCell.xib b/Riot/Modules/Common/Recents/Views/RecentTableViewCell.xib index 0303e1aca0..8fd41d2718 100644 --- a/Riot/Modules/Common/Recents/Views/RecentTableViewCell.xib +++ b/Riot/Modules/Common/Recents/Views/RecentTableViewCell.xib @@ -1,11 +1,11 @@ - + - + @@ -75,10 +75,10 @@ @@ -112,11 +112,11 @@ - + - + diff --git a/Riot/Modules/Home/Views/RoomCollectionViewCell.xib b/Riot/Modules/Home/Views/RoomCollectionViewCell.xib index b0c964fa13..ea0162bc9e 100644 --- a/Riot/Modules/Home/Views/RoomCollectionViewCell.xib +++ b/Riot/Modules/Home/Views/RoomCollectionViewCell.xib @@ -1,12 +1,11 @@ - + - - + @@ -39,7 +38,7 @@ - + @@ -108,7 +107,7 @@ - + From beb185dc026bbf59ddcb3af72c432bbae1f42fa4 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 31 Jan 2020 17:46:03 +0100 Subject: [PATCH 150/282] User verification: Add possibility to present on session detail from UserVerificationCoordinator. --- ...ificationSessionStatusViewController.swift | 2 ++ .../UserVerificationCoordinator.swift | 31 ++++++++++++++++--- ...rificationCoordinatorBridgePresenter.swift | 20 +++++++++++- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewController.swift b/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewController.swift index 748421b2ed..c358c4770e 100644 --- a/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewController.swift +++ b/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewController.swift @@ -147,6 +147,8 @@ final class UserVerificationSessionStatusViewController: UIViewController { let badgeImage: UIImage let title: String + self.untrustedSessionContainerView.isHidden = viewData.isDeviceTrusted + if viewData.isDeviceTrusted { badgeImage = Asset.Images.encryptionTrusted.image title = "Trusted" diff --git a/Riot/Modules/UserVerification/UserVerificationCoordinator.swift b/Riot/Modules/UserVerification/UserVerificationCoordinator.swift index 8fcd1c2392..3efcb6eabb 100644 --- a/Riot/Modules/UserVerification/UserVerificationCoordinator.swift +++ b/Riot/Modules/UserVerification/UserVerificationCoordinator.swift @@ -30,6 +30,7 @@ final class UserVerificationCoordinator: NSObject, UserVerificationCoordinatorTy private let session: MXSession private let userId: String private let userDisplayName: String? + private var deviceId: String? // MARK: Public @@ -48,6 +49,11 @@ final class UserVerificationCoordinator: NSObject, UserVerificationCoordinatorTy self.userDisplayName = userDisplayName } + convenience init(presenter: Presentable, session: MXSession, userId: String, userDisplayName: String?, deviceId: String) { + self.init(presenter: presenter, session: session, userId: userId, userDisplayName: userDisplayName) + self.deviceId = deviceId + } + // MARK: - Public methods func start() { @@ -56,8 +62,14 @@ final class UserVerificationCoordinator: NSObject, UserVerificationCoordinatorTy return } - let rootCoordinator = UserVerificationSessionsStatusCoordinator(session: self.session, userId: self.userId) - rootCoordinator.delegate = self + let rootCoordinator: Coordinator & Presentable + + if let deviceId = self.deviceId { + rootCoordinator = self.createSessionStatusCoordinator(with: deviceId, for: self.userId, userDisplayName: self.userDisplayName) + } else { + rootCoordinator = self.createUserVerificationSessionsStatusCoordinator() + } + rootCoordinator.start() self.add(childCoordinator: rootCoordinator) @@ -70,7 +82,7 @@ final class UserVerificationCoordinator: NSObject, UserVerificationCoordinatorTy rootViewController.modalPresentationStyle = .formSheet self.presenter.toPresentable().present(rootViewController, animated: true, completion: nil) - } + } func toPresentable() -> UIViewController { return self.navigationRouter.toPresentable() @@ -78,9 +90,20 @@ final class UserVerificationCoordinator: NSObject, UserVerificationCoordinatorTy // MARK: - Private methods - private func presentSessionStatus(with deviceId: String, for userId: String, userDisplayName: String?) { + private func createUserVerificationSessionsStatusCoordinator() -> UserVerificationSessionsStatusCoordinator { + let coordinator = UserVerificationSessionsStatusCoordinator(session: self.session, userId: self.userId) + coordinator.delegate = self + return coordinator + } + + private func createSessionStatusCoordinator(with deviceId: String, for userId: String, userDisplayName: String?) -> UserVerificationSessionStatusCoordinator { let coordinator = UserVerificationSessionStatusCoordinator(session: self.session, userId: userId, userDisplayName: userDisplayName, deviceId: deviceId) coordinator.delegate = self + return coordinator + } + + private func presentSessionStatus(with deviceId: String, for userId: String, userDisplayName: String?) { + let coordinator = self.createSessionStatusCoordinator(with: deviceId, for: userId, userDisplayName: userDisplayName) coordinator.start() self.navigationRouter.push(coordinator, animated: true) { diff --git a/Riot/Modules/UserVerification/UserVerificationCoordinatorBridgePresenter.swift b/Riot/Modules/UserVerification/UserVerificationCoordinatorBridgePresenter.swift index 929449c773..af19e72f3d 100644 --- a/Riot/Modules/UserVerification/UserVerificationCoordinatorBridgePresenter.swift +++ b/Riot/Modules/UserVerification/UserVerificationCoordinatorBridgePresenter.swift @@ -35,6 +35,7 @@ final class UserVerificationCoordinatorBridgePresenter: NSObject { private let session: MXSession private let userId: String private let userDisplayName: String? + private var deviceId: String? private var coordinator: Coordinator? @@ -52,6 +53,15 @@ final class UserVerificationCoordinatorBridgePresenter: NSObject { super.init() } + init(presenter: UIViewController, session: MXSession, userId: String, userDisplayName: String?, deviceId: String) { + self.presenter = presenter + self.session = session + self.userId = userId + self.userDisplayName = userDisplayName + self.deviceId = deviceId + super.init() + } + // MARK: - Public func start() { @@ -59,7 +69,15 @@ final class UserVerificationCoordinatorBridgePresenter: NSObject { } func present() { - let userVerificationCoordinator = UserVerificationCoordinator(presenter: self.presenter, session: self.session, userId: self.userId, userDisplayName: self.userDisplayName) + + let userVerificationCoordinator: UserVerificationCoordinator + + if let deviceId = self.deviceId { + userVerificationCoordinator = UserVerificationCoordinator(presenter: self.presenter, session: self.session, userId: self.userId, userDisplayName: self.userDisplayName, deviceId: deviceId) + } else { + userVerificationCoordinator = UserVerificationCoordinator(presenter: self.presenter, session: self.session, userId: self.userId, userDisplayName: self.userDisplayName) + } + userVerificationCoordinator.start() self.coordinator = userVerificationCoordinator } From 3ff40c59a2a837adf83715d01b1243e2772f0655 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 31 Jan 2020 17:46:16 +0100 Subject: [PATCH 151/282] Settings: Add session verification screens. --- .../ManageSession/ManageSessionViewController.m | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m index be916dffba..328e54a7de 100644 --- a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m +++ b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m @@ -62,6 +62,8 @@ @interface ManageSessionViewController () UIViewController *pushedViewController; } +@property (nonatomic, strong) UserVerificationCoordinatorBridgePresenter *userVerificationCoordinatorBridgePresenter; + @end @implementation ManageSessionViewController @@ -636,7 +638,13 @@ - (void)renameDevice - (void)showTrustForDevice:(MXDevice *)device { - [[AppDelegate theDelegate] showAlertWithTitle:@"Device Trust" message:@"TODO with bottom sheet 😛"]; + UserVerificationCoordinatorBridgePresenter *userVerificationCoordinatorBridgePresenter = [[UserVerificationCoordinatorBridgePresenter alloc] initWithPresenter:self + session:self.mainSession + userId:self.mainSession.myUser.userId + userDisplayName:nil + deviceId:device.deviceId]; + [userVerificationCoordinatorBridgePresenter start]; + self.userVerificationCoordinatorBridgePresenter = userVerificationCoordinatorBridgePresenter; } - (void)removeDevice From 09c77e61425a3eb733389d5223de1597cd109d8c Mon Sep 17 00:00:00 2001 From: manuroe Date: Sat, 1 Feb 2020 17:25:23 +0100 Subject: [PATCH 152/282] Room e2e decoration: Change the algo a bit --- Riot/Categories/MXRoomSummary+Riot.m | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/Riot/Categories/MXRoomSummary+Riot.m b/Riot/Categories/MXRoomSummary+Riot.m index fb13dccc4a..688f9d2c8a 100644 --- a/Riot/Categories/MXRoomSummary+Riot.m +++ b/Riot/Categories/MXRoomSummary+Riot.m @@ -52,22 +52,25 @@ - (RoomEncryptionTrustLevel)roomEncryptionTrustLevel RoomEncryptionTrustLevel roomEncryptionTrustLevel = RoomEncryptionTrustLevelUnknown; if (self.trust) { + double trustedUsersPercentage = self.trust.trustedUsersProgress.fractionCompleted; double trustedDevicesPercentage = self.trust.trustedDevicesProgress.fractionCompleted; - - if (trustedDevicesPercentage >= 1.0 - || self.trust.trustedDevicesProgress.totalUnitCount == 0) + + if (trustedUsersPercentage >= 1.0) { - roomEncryptionTrustLevel = RoomEncryptionTrustLevelTrusted; - } - else if (trustedDevicesPercentage == 0.0) - { - roomEncryptionTrustLevel = RoomEncryptionTrustLevelNormal; + if (trustedDevicesPercentage >= 1.0) + { + roomEncryptionTrustLevel = RoomEncryptionTrustLevelTrusted; + } + else + { + roomEncryptionTrustLevel = RoomEncryptionTrustLevelWarning; + } } else { - roomEncryptionTrustLevel = RoomEncryptionTrustLevelWarning; + roomEncryptionTrustLevel = RoomEncryptionTrustLevelNormal; } - + roomEncryptionTrustLevel = roomEncryptionTrustLevel; } From 4b04918546a74a216bd37d5891742ec9af1c8721 Mon Sep 17 00:00:00 2001 From: Osoitz Date: Sat, 1 Feb 2020 09:57:44 +0000 Subject: [PATCH 153/282] Translated using Weblate (Basque) Currently translated at 99.0% (868 of 877 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/eu/ --- Riot/Assets/eu.lproj/Vector.strings | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Riot/Assets/eu.lproj/Vector.strings b/Riot/Assets/eu.lproj/Vector.strings index 7ed7ef4cce..8c7879fa54 100644 --- a/Riot/Assets/eu.lproj/Vector.strings +++ b/Riot/Assets/eu.lproj/Vector.strings @@ -942,3 +942,11 @@ "key_verification_tile_conclusion_done_title" = "Egiaztatuta"; "key_verification_tile_conclusion_warning_title" = "Fidagarritasun gabeko saio hasiera"; "key_verification_incoming_request_incoming_alert_message" = "%@(e)k egiaztatu nahi du"; +"settings_security" = "SEGURTASUNA"; +"settings_labs_enable_cross_signing" = "Gaitu zeharkako sinatzea erabiltzaileko egiaztatzeko eta ez saioko (garapenean)"; +// Security settings +"security_settings_title" = "Segurtasuna"; +"security_settings_crypto_sessions" = "NIRE SAIOAK"; +"security_settings_backup" = "MEZUEN BABES-KOPIA"; +"security_settings_advanced" = "AURRERATUA"; +"security_settings_blacklist_unverified_devices" = "Ez bidali inoiz mezuak egiaztatu gabeko saioetara"; From af6ba287ced985c46543cd8e3961cf8116b9de5d Mon Sep 17 00:00:00 2001 From: Szimszon Date: Sat, 1 Feb 2020 17:10:21 +0000 Subject: [PATCH 154/282] Translated using Weblate (Hungarian) Currently translated at 100.0% (877 of 877 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/hu/ --- Riot/Assets/hu.lproj/Vector.strings | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index b654549b91..6ec5f53914 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -960,3 +960,20 @@ "key_verification_tile_conclusion_warning_title" = "Megbízhatatlan belépés"; "key_verification_incoming_request_incoming_alert_message" = "%@ ellenőrizni szeretné"; "settings_labs_enable_cross_signing" = "Kereszt-aláírás engedélyezése a felhasználó alapú azonosításhoz az eszköz alapú helyett (fejlesztés alatt)"; +"settings_security" = "BIZTONSÁG"; +// Security settings +"security_settings_title" = "Biztonság"; +"security_settings_crypto_sessions" = "MUNKAMENETEIM"; +"security_settings_crypto_sessions_description" = "Bízz meg a munkamenetbe a végpontok között titkosított üzenetek hozzáféréséhez. Ha nem ismered fel a munkamenetet, változtasd meg a bejelentkezési jelszavad és az Üzenet Mentéshez használt jelszavad."; +"security_settings_backup" = "ÜZENET MENTÉS"; +"security_settings_advanced" = "HALADÓ"; +"security_settings_blacklist_unverified_devices" = "Soha ne küldj üzenetet megbízhatatlan munkamenetekbe"; +"security_settings_blacklist_unverified_devices_description" = "Ellenőrizd minden felhasználó minden munkamenetét, hogy megbízhatónak tudd jelölni azokat és üzenet küldhess nekik."; +"security_settings_export_keys_manually" = "Kulcsok kimentése kézzel"; +// Manage session +"manage_session_title" = "Munkamenet kezelése"; +"manage_session_info" = "MUNKAMENET INFORMÁCIÓ"; +"manage_session_name" = "Eszköznév"; +"manage_session_trusted" = "Szerinted megbízható"; +"manage_session_not_trusted" = "Megbízhatatlan"; +"manage_session_sign_out" = "Kijelentkezés erről az eszközről"; From 34a3de9f0be912c08129285d169cf6a24361eaae Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Mon, 3 Feb 2020 17:48:25 +0100 Subject: [PATCH 155/282] Add user verification strings. --- Riot/Assets/en.lproj/Vector.strings | 66 ++++++++++++- Riot/Generated/Strings.swift | 142 +++++++++++++++++++++++++++- 2 files changed, 206 insertions(+), 2 deletions(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 06a6177b2b..b2da90f556 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -240,7 +240,8 @@ "room_participants_action_section_admin_tools" = "Admin tools"; "room_participants_action_section_direct_chats" = "Direct chats"; "room_participants_action_section_devices" = "Devices"; -"room_participants_action_section_other" = "Other"; +"room_participants_action_section_other" = "Options"; +"room_participants_action_section_security" = "Security"; "room_participants_action_invite" = "Invite"; "room_participants_action_leave" = "Leave this room"; @@ -256,6 +257,13 @@ "room_participants_action_start_voice_call" = "Start voice call"; "room_participants_action_start_video_call" = "Start video call"; "room_participants_action_mention" = "Mention"; +"room_participants_action_security_status_verified" = "Verified"; +"room_participants_action_security_status_verify" = "Verify"; +"room_participants_action_security_status_warning" = "Warning"; + +"room_participants_security_loading" = "Loading…"; +"room_participants_security_information_room_not_encrypted" = "Messages in this room are not end-to-end encrypted."; +"room_participants_security_information_room_encrypted" = "Messages in this room are end-to-end encrypted.\n\nYour messages are secured with locks and only you and the recipient have the unique keys to unlock them."; // Chat "room_jump_to_first_unread" = "Jump to first unread message"; @@ -993,6 +1001,8 @@ // MARK: - Device Verification "device_verification_title" = "Verify device"; +"key_verification_user_title" = "Verify user"; + "device_verification_security_advice" = "For maximum security, we recommend you do this in person or use another trusted means of communication"; "device_verification_cancelled" = "The other party cancelled the verification."; "device_verification_cancelled_by_me" = "The verification has been cancelled. Reason: %@"; @@ -1011,16 +1021,32 @@ "device_verification_start_use_legacy_action" = "Use Legacy Verification"; // MARK: Verify + +// Device + "device_verification_verify_title_emoji" = "Verify this device by confirming the following emoji appear on the screen of the partner"; "device_verification_verify_title_number" = "Verify this device by confirming the following numbers appear on the screen of the partner"; "device_verification_verify_wait_partner" = "Waiting for partner to confirm..."; +// User + +"key_verification_verify_user_title_emoji" = "Verify this user by confirming the following unique emoji appears on their screen, in the same order."; +"key_verification_verify_user_title_number" = "Verify this user by confirming the following numbers appear on their screen, in the same order."; + // MARK: Verified + +// Device + "device_verification_verified_title" = "Verified!"; "device_verification_verified_description_1" = "You've successfully verified this device."; "device_verification_verified_description_2" = "Secure messages with this user are end-to-end encrypted and not able to be read by third parties."; "device_verification_verified_got_it_button" = "Got it"; +// User + +"key_verification_verified_user_description_1" = "You’ve successfully verified this user."; +"key_verification_verified_user_description_2" = "Messages with this user in this room are end-to-end encrypted and can’t be read by third parties."; + // MARK: Emoji "device_verification_emoji_dog" = "Dog"; "device_verification_emoji_cat" = "Cat"; @@ -1134,3 +1160,41 @@ // Incoming key verification request "key_verification_incoming_request_incoming_alert_message" = "%@ wants to verify"; + +// MARK: - User verification + +// Start + +"user_verification_start_verify_action" = "Start verification"; +"user_verification_start_information_part1" = "For extra security, verify "; +"user_verification_start_information_part2" = " by checking a one-time code on both your devices."; +"user_verification_start_waiting_partner" = "Waiting for %@…"; +"user_verification_start_additional_information" = "To be secure, do this in person or use another way to communicate."; + +// Sessions list + +"user_verification_sessions_list_user_trust_level_trusted_title" = "Trusted"; +"user_verification_sessions_list_user_trust_level_warning_title" = "Warning"; +"user_verification_sessions_list_user_trust_level_unknown_title" = "Unknown"; +"user_verification_sessions_list_information" = "Messages with this user in this room are end-to-end encrypted and can’t be read by third parties."; +"user_verification_sessions_list_table_title" = "Sessions"; +"user_verification_sessions_list_session_trusted" = "Trusted"; +"user_verification_sessions_list_session_untrusted" = "Not trusted"; + +// Session details + +"user_verification_session_details_trusted_title" = "Trusted"; +"user_verification_session_details_untrusted_title" = "Warning"; + +"user_verification_session_details_information_trusted_current_user" = "This session is trusted for secure messaging because you verified it:"; +"user_verification_session_details_information_trusted_other_user_part1" = "This device is trusted for secure messaging because "; +"user_verification_session_details_information_trusted_other_user_part2" = " verified it:"; + +"user_verification_session_details_information_untrusted_current_user" = "Verify this session to mark it as trusted & grant it access to encrypted messages:"; +"user_verification_session_details_information_untrusted_other_user" = " signed in using a new device:"; + +"user_verification_session_details_additional_information_untrusted_other_user" = "Until this user trusts this device, messages sent to and from it are labelled with warnings. Alternatively, you can manually verify it."; +"user_verification_session_details_additional_information_untrusted_current_user" = "If you didn’t sign in to this session, your account may be compromised."; + +"user_verification_session_details_verify_action_current_user" = "Verify"; +"user_verification_session_details_verify_action_other_user" = "Manually verify"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 388a956d7a..8ba0033209 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -1522,6 +1522,26 @@ internal enum VectorL10n { internal static var keyVerificationTileRequestStatusWaiting: String { return VectorL10n.tr("Vector", "key_verification_tile_request_status_waiting") } + /// Verify user + internal static var keyVerificationUserTitle: String { + return VectorL10n.tr("Vector", "key_verification_user_title") + } + /// You’ve successfully verified this user. + internal static var keyVerificationVerifiedUserDescription1: String { + return VectorL10n.tr("Vector", "key_verification_verified_user_description_1") + } + /// Messages with this user in this room are end-to-end encrypted and can’t be read by third parties. + internal static var keyVerificationVerifiedUserDescription2: String { + return VectorL10n.tr("Vector", "key_verification_verified_user_description_2") + } + /// Verify this user by confirming the following unique emoji appears on their screen, in the same order. + internal static var keyVerificationVerifyUserTitleEmoji: String { + return VectorL10n.tr("Vector", "key_verification_verify_user_title_emoji") + } + /// Verify this user by confirming the following numbers appear on their screen, in the same order. + internal static var keyVerificationVerifyUserTitleNumber: String { + return VectorL10n.tr("Vector", "key_verification_verify_user_title_number") + } /// %.1fK internal static func largeBadgeValueKFormat(_ p1: Float) -> String { return VectorL10n.tr("Vector", "large_badge_value_k_format", p1) @@ -2254,10 +2274,26 @@ internal enum VectorL10n { internal static var roomParticipantsActionSectionDirectChats: String { return VectorL10n.tr("Vector", "room_participants_action_section_direct_chats") } - /// Other + /// Options internal static var roomParticipantsActionSectionOther: String { return VectorL10n.tr("Vector", "room_participants_action_section_other") } + /// Security + internal static var roomParticipantsActionSectionSecurity: String { + return VectorL10n.tr("Vector", "room_participants_action_section_security") + } + /// Verified + internal static var roomParticipantsActionSecurityStatusVerified: String { + return VectorL10n.tr("Vector", "room_participants_action_security_status_verified") + } + /// Verify + internal static var roomParticipantsActionSecurityStatusVerify: String { + return VectorL10n.tr("Vector", "room_participants_action_security_status_verify") + } + /// Warning + internal static var roomParticipantsActionSecurityStatusWarning: String { + return VectorL10n.tr("Vector", "room_participants_action_security_status_warning") + } /// Make admin internal static var roomParticipantsActionSetAdmin: String { return VectorL10n.tr("Vector", "room_participants_action_set_admin") @@ -2370,6 +2406,18 @@ internal enum VectorL10n { internal static var roomParticipantsRemoveThirdPartyInvitePromptMsg: String { return VectorL10n.tr("Vector", "room_participants_remove_third_party_invite_prompt_msg") } + /// Messages in this room are end-to-end encrypted.\n\nYour messages are secured with locks and only you and the recipient have the unique keys to unlock them. + internal static var roomParticipantsSecurityInformationRoomEncrypted: String { + return VectorL10n.tr("Vector", "room_participants_security_information_room_encrypted") + } + /// Messages in this room are not end-to-end encrypted. + internal static var roomParticipantsSecurityInformationRoomNotEncrypted: String { + return VectorL10n.tr("Vector", "room_participants_security_information_room_not_encrypted") + } + /// Loading… + internal static var roomParticipantsSecurityLoading: String { + return VectorL10n.tr("Vector", "room_participants_security_loading") + } /// No identity server is configured so you cannot start a chat with a contact using an email. internal static var roomParticipantsStartNewChatErrorUsingUserEmailWithoutIdentityServer: String { return VectorL10n.tr("Vector", "room_participants_start_new_chat_error_using_user_email_without_identity_server") @@ -3402,6 +3450,98 @@ internal enum VectorL10n { internal static var unknownDevicesVerify: String { return VectorL10n.tr("Vector", "unknown_devices_verify") } + /// If you didn’t sign in to this session, your account may be compromised. + internal static var userVerificationSessionDetailsAdditionalInformationUntrustedCurrentUser: String { + return VectorL10n.tr("Vector", "user_verification_session_details_additional_information_untrusted_current_user") + } + /// Until this user trusts this device, messages sent to and from it are labelled with warnings. Alternatively, you can manually verify it. + internal static var userVerificationSessionDetailsAdditionalInformationUntrustedOtherUser: String { + return VectorL10n.tr("Vector", "user_verification_session_details_additional_information_untrusted_other_user") + } + /// This session is trusted for secure messaging because you verified it: + internal static var userVerificationSessionDetailsInformationTrustedCurrentUser: String { + return VectorL10n.tr("Vector", "user_verification_session_details_information_trusted_current_user") + } + /// This device is trusted for secure messaging because + internal static var userVerificationSessionDetailsInformationTrustedOtherUserPart1: String { + return VectorL10n.tr("Vector", "user_verification_session_details_information_trusted_other_user_part1") + } + /// verified it: + internal static var userVerificationSessionDetailsInformationTrustedOtherUserPart2: String { + return VectorL10n.tr("Vector", "user_verification_session_details_information_trusted_other_user_part2") + } + /// Verify this session to mark it as trusted & grant it access to encrypted messages: + internal static var userVerificationSessionDetailsInformationUntrustedCurrentUser: String { + return VectorL10n.tr("Vector", "user_verification_session_details_information_untrusted_current_user") + } + /// signed in using a new device: + internal static var userVerificationSessionDetailsInformationUntrustedOtherUser: String { + return VectorL10n.tr("Vector", "user_verification_session_details_information_untrusted_other_user") + } + /// Trusted + internal static var userVerificationSessionDetailsTrustedTitle: String { + return VectorL10n.tr("Vector", "user_verification_session_details_trusted_title") + } + /// Warning + internal static var userVerificationSessionDetailsUntrustedTitle: String { + return VectorL10n.tr("Vector", "user_verification_session_details_untrusted_title") + } + /// Verify + internal static var userVerificationSessionDetailsVerifyActionCurrentUser: String { + return VectorL10n.tr("Vector", "user_verification_session_details_verify_action_current_user") + } + /// Manually verify + internal static var userVerificationSessionDetailsVerifyActionOtherUser: String { + return VectorL10n.tr("Vector", "user_verification_session_details_verify_action_other_user") + } + /// Messages with this user in this room are end-to-end encrypted and can’t be read by third parties. + internal static var userVerificationSessionsListInformation: String { + return VectorL10n.tr("Vector", "user_verification_sessions_list_information") + } + /// Trusted + internal static var userVerificationSessionsListSessionTrusted: String { + return VectorL10n.tr("Vector", "user_verification_sessions_list_session_trusted") + } + /// Not trusted + internal static var userVerificationSessionsListSessionUntrusted: String { + return VectorL10n.tr("Vector", "user_verification_sessions_list_session_untrusted") + } + /// Sessions + internal static var userVerificationSessionsListTableTitle: String { + return VectorL10n.tr("Vector", "user_verification_sessions_list_table_title") + } + /// Trusted + internal static var userVerificationSessionsListUserTrustLevelTrustedTitle: String { + return VectorL10n.tr("Vector", "user_verification_sessions_list_user_trust_level_trusted_title") + } + /// Unknown + internal static var userVerificationSessionsListUserTrustLevelUnknownTitle: String { + return VectorL10n.tr("Vector", "user_verification_sessions_list_user_trust_level_unknown_title") + } + /// Warning + internal static var userVerificationSessionsListUserTrustLevelWarningTitle: String { + return VectorL10n.tr("Vector", "user_verification_sessions_list_user_trust_level_warning_title") + } + /// To be secure, do this in person or use another way to communicate. + internal static var userVerificationStartAdditionalInformation: String { + return VectorL10n.tr("Vector", "user_verification_start_additional_information") + } + /// For extra security, verify + internal static var userVerificationStartInformationPart1: String { + return VectorL10n.tr("Vector", "user_verification_start_information_part1") + } + /// by checking a one-time code on both your devices. + internal static var userVerificationStartInformationPart2: String { + return VectorL10n.tr("Vector", "user_verification_start_information_part2") + } + /// Start verification + internal static var userVerificationStartVerifyAction: String { + return VectorL10n.tr("Vector", "user_verification_start_verify_action") + } + /// Waiting for %@… + internal static func userVerificationStartWaitingPartner(_ p1: String) -> String { + return VectorL10n.tr("Vector", "user_verification_start_waiting_partner", p1) + } /// Video internal static var video: String { return VectorL10n.tr("Vector", "video") From 72da7b1f2e774d30ad75a8b0878f81f37a81b47f Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Mon, 3 Feb 2020 17:50:52 +0100 Subject: [PATCH 156/282] RoomMemberDetailsViewController: Update security section and use localization strings. --- .../Detail/RoomMemberDetailsViewController.m | 129 ++++++++++-------- 1 file changed, 74 insertions(+), 55 deletions(-) diff --git a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m index 3c49b1ef07..2061b35107 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m @@ -130,6 +130,7 @@ - (void)finalizeInit self.rageShakeManager = [RageShakeManager sharedManager]; self.encryptionTrustLevel = UserEncryptionTrustLevelUnknown; + securityActionsArray = [[NSMutableArray alloc] init]; adminActionsArray = [[NSMutableArray alloc] init]; otherActionsArray = [[NSMutableArray alloc] init]; directChatsArray = [[NSMutableArray alloc] init]; @@ -606,6 +607,7 @@ - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView NSInteger memberPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mxRoomMember.userId]; NSInteger oneSelfPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; + [securityActionsArray removeAllObjects]; [adminActionsArray removeAllObjects]; [otherActionsArray removeAllObjects]; @@ -744,9 +746,29 @@ - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView } } + if (RiotSettings.shared.enableCrossSigning) + { + switch (self.encryptionTrustLevel) { + case UserEncryptionTrustLevelUnknown: + [securityActionsArray addObject:@(MXKRoomMemberDetailsActionSecurityInformation)]; + break; + case UserEncryptionTrustLevelNone: + case UserEncryptionTrustLevelNormal: + case UserEncryptionTrustLevelTrusted: + case UserEncryptionTrustLevelWarning: + [securityActionsArray addObjectsFromArray:@[@(MXKRoomMemberDetailsActionSecurity), + @(MXKRoomMemberDetailsActionSecurityInformation) + ]]; + break; + default: + break; + } + } + securityIndex = adminToolsIndex = otherActionsIndex = directChatsIndex = devicesIndex = -1; - if (RiotSettings.shared.enableCrossSigning) + + if (securityActionsArray.count) { securityIndex = sectionCount++; } @@ -777,19 +799,7 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger { if (section == securityIndex) { - NSInteger numberOfRows; - - switch (self.encryptionTrustLevel) { - case UserEncryptionTrustLevelUnknown: - case UserEncryptionTrustLevelNone: - numberOfRows = 1; - break; - default: - numberOfRows = 2; - break; - } - - return numberOfRows; + return securityActionsArray.count; } else if (section == adminToolsIndex) { @@ -815,7 +825,7 @@ - (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSectio { if (section == securityIndex) { - return @"SECURITY"; + return NSLocalizedStringFromTable(@"room_participants_action_section_security", @"Vector", nil); } else if (section == adminToolsIndex) { @@ -823,7 +833,7 @@ - (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSectio } else if (RiotSettings.shared.enableCrossSigning && section == otherActionsIndex) { - return @"OPTIONS"; + return NSLocalizedStringFromTable(@"room_participants_action_section_other", @"Vector", nil); } else if (section == directChatsIndex) { @@ -896,42 +906,11 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N { UITableViewCell *cell; - if (indexPath.section == securityIndex) + if (indexPath.section == securityIndex && indexPath.row < securityActionsArray.count) { - if (indexPath.row == [self tableView:self.tableView numberOfRowsInSection:indexPath.section] - 1) - { - MXKTableViewCell *encryptionInfoCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCell defaultReuseIdentifier] forIndexPath:indexPath]; - - NSMutableString *encryptionInformation = [NSMutableString new]; - - switch (self.encryptionTrustLevel) { - case UserEncryptionTrustLevelUnknown: - [encryptionInformation appendString:@"Loading"]; - break; - case UserEncryptionTrustLevelNone: - [encryptionInformation appendString:@"Messages in this room are not end-to-end encrypted."]; - break; - default: - [encryptionInformation appendString:@"Messages in this room are end-to-end encrypted.\n\nYour messages are secured with locks and only you and the recipient have the unique keys to unlock them."]; - break; - } - - [encryptionInformation appendString:@"\n"]; - - encryptionInfoCell.textLabel.backgroundColor = [UIColor clearColor]; - encryptionInfoCell.textLabel.numberOfLines = 0; - encryptionInfoCell.textLabel.text = encryptionInformation; - encryptionInfoCell.textLabel.font = [UIFont systemFontOfSize:14.0]; - encryptionInfoCell.textLabel.textColor = ThemeService.shared.theme.headerTextPrimaryColor; - - encryptionInfoCell.selectionStyle = UITableViewCellSelectionStyleNone; - encryptionInfoCell.accessoryType = UITableViewCellAccessoryNone; - encryptionInfoCell.contentView.backgroundColor = ThemeService.shared.theme.headerBackgroundColor; - encryptionInfoCell.backgroundColor = ThemeService.shared.theme.headerBackgroundColor; - - cell = encryptionInfoCell; - } - else + NSNumber *actionNumber = securityActionsArray[indexPath.row]; + + if (actionNumber.unsignedIntegerValue == MXKRoomMemberDetailsActionSecurity) { MXKTableViewCell *securityStatusCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCell defaultReuseIdentifier] forIndexPath:indexPath]; @@ -939,16 +918,16 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N switch (self.encryptionTrustLevel) { case UserEncryptionTrustLevelTrusted: - statusText = @"Verified"; + statusText = NSLocalizedStringFromTable(@"room_participants_action_security_status_verified", @"Vector", nil); break; case UserEncryptionTrustLevelNormal: - statusText = @"Verify"; + statusText = NSLocalizedStringFromTable(@"room_participants_action_security_status_verify", @"Vector", nil); break; case UserEncryptionTrustLevelWarning: - statusText = @"Warning"; + statusText = NSLocalizedStringFromTable(@"room_participants_action_security_status_warning", @"Vector", nil); break; default: - statusText = @"Loading"; + statusText = NSLocalizedStringFromTable(@"room_participants_action_security_status_loading", @"Vector", nil); break; } @@ -966,6 +945,46 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N cell = securityStatusCell; } + else if (actionNumber.unsignedIntegerValue == MXKRoomMemberDetailsActionSecurityInformation) + { + MXKTableViewCell *encryptionInfoCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCell defaultReuseIdentifier] forIndexPath:indexPath]; + + NSMutableString *encryptionInformation = [NSMutableString new]; + + switch (self.encryptionTrustLevel) { + case UserEncryptionTrustLevelWarning: + case UserEncryptionTrustLevelNormal: + case UserEncryptionTrustLevelTrusted: + [encryptionInformation appendString:NSLocalizedStringFromTable(@"room_participants_security_information_room_encrypted", @"Vector", nil)]; + break; + case UserEncryptionTrustLevelNone: + [encryptionInformation appendString:NSLocalizedStringFromTable(@"room_participants_security_information_room_not_encrypted", @"Vector", nil)]; + break; + case UserEncryptionTrustLevelUnknown: + [encryptionInformation appendString:NSLocalizedStringFromTable(@"room_participants_security_information_loading", @"Vector", nil)]; + break; + default: + break; + } + + if (encryptionInformation.length) + { + [encryptionInformation appendString:@"\n"]; + } + + encryptionInfoCell.textLabel.backgroundColor = [UIColor clearColor]; + encryptionInfoCell.textLabel.numberOfLines = 0; + encryptionInfoCell.textLabel.text = encryptionInformation; + encryptionInfoCell.textLabel.font = [UIFont systemFontOfSize:14.0]; + encryptionInfoCell.textLabel.textColor = ThemeService.shared.theme.headerTextPrimaryColor; + + encryptionInfoCell.selectionStyle = UITableViewCellSelectionStyleNone; + encryptionInfoCell.accessoryType = UITableViewCellAccessoryNone; + encryptionInfoCell.contentView.backgroundColor = ThemeService.shared.theme.headerBackgroundColor; + encryptionInfoCell.backgroundColor = ThemeService.shared.theme.headerBackgroundColor; + + cell = encryptionInfoCell; + } } else if (indexPath.section == adminToolsIndex || indexPath.section == otherActionsIndex) { From ddfe4554f95698be5cf78b0e2615c8690b15aa20 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Mon, 3 Feb 2020 17:52:39 +0100 Subject: [PATCH 157/282] User verification: Use localization strings. --- ...ceVerificationVerifiedViewController.swift | 6 ++--- ...viceVerificationVerifyViewController.swift | 4 ++-- ...ificationSessionStatusViewController.swift | 22 +++++++++---------- .../UserVerificationSessionStatusCell.swift | 4 ++-- ...ficationSessionsStatusViewController.swift | 10 ++++----- .../UserVerificationStartViewController.swift | 11 +++++----- 6 files changed, 29 insertions(+), 28 deletions(-) diff --git a/Riot/Modules/DeviceVerification/Verified/DeviceVerificationVerifiedViewController.swift b/Riot/Modules/DeviceVerification/Verified/DeviceVerificationVerifiedViewController.swift index a583410bd1..7c6aad04cd 100644 --- a/Riot/Modules/DeviceVerification/Verified/DeviceVerificationVerifiedViewController.swift +++ b/Riot/Modules/DeviceVerification/Verified/DeviceVerificationVerifiedViewController.swift @@ -94,10 +94,10 @@ final class DeviceVerificationVerifiedViewController: UIViewController { descriptionTextPart1 = VectorL10n.deviceVerificationVerifiedDescription1 descriptionTextPart2 = VectorL10n.deviceVerificationVerifiedDescription2 case .user: - title = "Verify user" + title = VectorL10n.keyVerificationUserTitle bodyTitle = VectorL10n.deviceVerificationVerifiedTitle - descriptionTextPart1 = "You’ve successfully verified this user." - descriptionTextPart2 = "Messages with this user in this room are end-to-end encrypted and can’t be read by third parties." + descriptionTextPart1 = VectorL10n.keyVerificationVerifiedUserDescription1 + descriptionTextPart2 = VectorL10n.keyVerificationVerifiedUserDescription2 } self.title = title diff --git a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewController.swift b/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewController.swift index 69065a7514..6adef540e4 100644 --- a/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewController.swift +++ b/Riot/Modules/DeviceVerification/Verify/DeviceVerificationVerifyViewController.swift @@ -141,8 +141,8 @@ final class DeviceVerificationVerifyViewController: UIViewController { instructionText = isVerificationByEmoji ? VectorL10n.deviceVerificationVerifyTitleEmoji : VectorL10n.deviceVerificationVerifyTitleNumber adviceText = VectorL10n.deviceVerificationSecurityAdvice case .user: - title = "Verify user" - instructionText = isVerificationByEmoji ? "Verify this user by confirming the following unique emoji appears on their screen, in the same order." : "Verify this user by confirming the following numbers appear on their screen, in the same order." + title = VectorL10n.keyVerificationUserTitle + instructionText = isVerificationByEmoji ? VectorL10n.keyVerificationVerifyUserTitleEmoji : VectorL10n.keyVerificationVerifyUserTitleNumber adviceText = VectorL10n.deviceVerificationSecurityAdvice } diff --git a/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewController.swift b/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewController.swift index c358c4770e..eb8d2ff657 100644 --- a/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewController.swift +++ b/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewController.swift @@ -151,21 +151,21 @@ final class UserVerificationSessionStatusViewController: UIViewController { if viewData.isDeviceTrusted { badgeImage = Asset.Images.encryptionTrusted.image - title = "Trusted" + title = VectorL10n.userVerificationSessionDetailsTrustedTitle } else { badgeImage = Asset.Images.encryptionWarning.image - title = "Warning" + title = VectorL10n.userVerificationSessionDetailsUntrustedTitle } let unstrustedInformationText: String let verifyButtonTitle: String if viewData.isCurrentUser { - unstrustedInformationText = "If you didn’t sign in to this session, your account may be compromised." - verifyButtonTitle = "Verify" + unstrustedInformationText = VectorL10n.userVerificationSessionDetailsInformationUntrustedCurrentUser + verifyButtonTitle = VectorL10n.userVerificationSessionDetailsVerifyActionCurrentUser } else { - unstrustedInformationText = "Until this user trusts this device, messages sent to and from it are labelled with warnings. Alternatively, you can manually verify it." - verifyButtonTitle = "Manually verify" + unstrustedInformationText = VectorL10n.userVerificationSessionDetailsInformationUntrustedOtherUser + verifyButtonTitle = VectorL10n.userVerificationSessionDetailsVerifyActionOtherUser } self.badgeImageView.image = badgeImage @@ -212,12 +212,12 @@ final class UserVerificationSessionStatusViewController: UIViewController { if viewData.isDeviceTrusted { if viewData.isCurrentUser { - let informationAttributedStringPart1 = NSAttributedString(string: "This session is trusted for secure messaging because you verified it:", attributes: informationTextDefaultAttributes) + let informationAttributedStringPart1 = NSAttributedString(string: VectorL10n.userVerificationSessionDetailsInformationTrustedCurrentUser, attributes: informationTextDefaultAttributes) informationAttributedText.append(informationAttributedStringPart1) } else { - let informationAttributedStringPart1 = NSAttributedString(string: "This device is trusted for secure messaging because ", attributes: informationTextDefaultAttributes) + let informationAttributedStringPart1 = NSAttributedString(string: VectorL10n.userVerificationSessionDetailsInformationTrustedOtherUserPart1, attributes: informationTextDefaultAttributes) let informationAttributedStringPart2 = NSAttributedString(string: userInfoText, attributes: informationTextBoldAttributes) - let informationAttributedStringPart3 = NSAttributedString(string: " verified it:", attributes: informationTextDefaultAttributes) + let informationAttributedStringPart3 = NSAttributedString(string: VectorL10n.userVerificationSessionDetailsInformationTrustedOtherUserPart2, attributes: informationTextDefaultAttributes) informationAttributedText.append(informationAttributedStringPart1) informationAttributedText.append(informationAttributedStringPart2) @@ -226,11 +226,11 @@ final class UserVerificationSessionStatusViewController: UIViewController { } else { if viewData.isCurrentUser { - let informationAttributedStringPart1 = NSAttributedString(string: "Verify this session to mark it as trusted & grant it access to encrypted messages:", attributes: informationTextDefaultAttributes) + let informationAttributedStringPart1 = NSAttributedString(string: VectorL10n.userVerificationSessionDetailsInformationUntrustedCurrentUser, attributes: informationTextDefaultAttributes) informationAttributedText.append(informationAttributedStringPart1) } else { let informationAttributedStringPart1 = NSAttributedString(string: userInfoText, attributes: informationTextBoldAttributes) - let informationAttributedStringPart2 = NSAttributedString(string: " signed in using a new device:", attributes: informationTextDefaultAttributes) + let informationAttributedStringPart2 = NSAttributedString(string: VectorL10n.userVerificationSessionDetailsInformationUntrustedOtherUser, attributes: informationTextDefaultAttributes) informationAttributedText.append(informationAttributedStringPart1) informationAttributedText.append(informationAttributedStringPart2) diff --git a/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionStatusCell.swift b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionStatusCell.swift index f281374f7a..d0b6de6e71 100644 --- a/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionStatusCell.swift +++ b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionStatusCell.swift @@ -48,10 +48,10 @@ final class UserVerificationSessionStatusCell: UITableViewCell, NibReusable, The if viewData.isTrusted { statusImage = Asset.Images.encryptionTrusted.image - statusText = "Trusted" + statusText = VectorL10n.userVerificationSessionsListSessionTrusted } else { statusImage = Asset.Images.encryptionWarning.image - statusText = "Not trusted" + statusText = VectorL10n.userVerificationSessionsListSessionUntrusted } self.statusImageView.image = statusImage diff --git a/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewController.swift b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewController.swift index 21c298dce8..b1384ba1f4 100644 --- a/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewController.swift +++ b/Riot/Modules/UserVerification/SessionsStatus/UserVerificationSessionsStatusViewController.swift @@ -123,8 +123,8 @@ final class UserVerificationSessionsStatusViewController: UIViewController { self.setupTableView() self.updateTitleViews() - self.sessionsTableViewTitle.text = "Sessions" - self.informationLabel.text = "Messages with this user in this room are end-to-end encrypted and can’t be read by third parties." + self.sessionsTableViewTitle.text = VectorL10n.userVerificationSessionsListTableTitle + self.informationLabel.text = VectorL10n.userVerificationSessionsListInformation } private func setupTableView() { @@ -178,13 +178,13 @@ final class UserVerificationSessionsStatusViewController: UIViewController { switch self.userEncryptionTrustLevel { case .trusted: badgeImage = Asset.Images.encryptionTrusted.image - title = "Trusted" + title = VectorL10n.userVerificationSessionsListUserTrustLevelTrustedTitle case .warning: badgeImage = Asset.Images.encryptionWarning.image - title = "Warning" + title = VectorL10n.userVerificationSessionsListUserTrustLevelWarningTitle default: badgeImage = Asset.Images.encryptionNormal.image - title = "Unknown" + title = VectorL10n.userVerificationSessionsListUserTrustLevelUnknownTitle } self.badgeImageImageView.image = badgeImage diff --git a/Riot/Modules/UserVerification/Start/UserVerificationStartViewController.swift b/Riot/Modules/UserVerification/Start/UserVerificationStartViewController.swift index b57301d750..aa0e901f71 100644 --- a/Riot/Modules/UserVerification/Start/UserVerificationStartViewController.swift +++ b/Riot/Modules/UserVerification/Start/UserVerificationStartViewController.swift @@ -62,7 +62,7 @@ final class UserVerificationStartViewController: UIViewController { // Do any additional setup after loading the view. - self.title = "Verify user" + self.title = VectorL10n.keyVerificationUserTitle self.setupViews() @@ -120,7 +120,8 @@ final class UserVerificationStartViewController: UIViewController { self.navigationItem.rightBarButtonItem = cancelBarButtonItem self.startVerificationButton.layer.masksToBounds = true - self.startVerificationButton.setTitle("Start verification", for: .normal) + self.startVerificationButton.setTitle(VectorL10n.userVerificationStartVerifyAction, for: .normal) + self.additionalInformationLabel.text = VectorL10n.userVerificationStartAdditionalInformation } private func render(viewState: UserVerificationStartViewState) { @@ -192,9 +193,9 @@ final class UserVerificationStartViewController: UIViewController { let informationTextBoldAttributes: [NSAttributedString.Key: Any] = [.foregroundColor: self.theme.textPrimaryColor, .font: Constants.informationTextBoldFont] - let informationAttributedStringPart1 = NSAttributedString(string: "For extra security, verify ", attributes: informationTextDefaultAttributes) + let informationAttributedStringPart1 = NSAttributedString(string: VectorL10n.userVerificationStartInformationPart1, attributes: informationTextDefaultAttributes) let informationAttributedStringPart2 = NSAttributedString(string: userId, attributes: informationTextBoldAttributes) - let informationAttributedStringPart3 = NSAttributedString(string: " by checking a one-time code on both your devices.", attributes: informationTextDefaultAttributes) + let informationAttributedStringPart3 = NSAttributedString(string: VectorL10n.userVerificationStartInformationPart2, attributes: informationTextDefaultAttributes) informationAttributedText.append(informationAttributedStringPart1) informationAttributedText.append(informationAttributedStringPart2) @@ -205,7 +206,7 @@ final class UserVerificationStartViewController: UIViewController { private func buildVerificationWaitingText(with viewData: UserVerificationStartViewData) -> String { let userName = viewData.userDisplayName ?? viewData.userId - return "Waiting for \(userName)…" + return VectorL10n.userVerificationStartWaitingPartner(userName) } // MARK: - Actions From bd131f12a9cb42797e805979f148bb3e4a771a66 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Mon, 3 Feb 2020 18:07:53 +0100 Subject: [PATCH 158/282] UserVerificationSessionStatusViewController: Fix wording issue. --- .../UserVerificationSessionStatusViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewController.swift b/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewController.swift index eb8d2ff657..01cdc04e05 100644 --- a/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewController.swift +++ b/Riot/Modules/UserVerification/SessionStatus/UserVerificationSessionStatusViewController.swift @@ -161,10 +161,10 @@ final class UserVerificationSessionStatusViewController: UIViewController { let verifyButtonTitle: String if viewData.isCurrentUser { - unstrustedInformationText = VectorL10n.userVerificationSessionDetailsInformationUntrustedCurrentUser + unstrustedInformationText = VectorL10n.userVerificationSessionDetailsAdditionalInformationUntrustedCurrentUser verifyButtonTitle = VectorL10n.userVerificationSessionDetailsVerifyActionCurrentUser } else { - unstrustedInformationText = VectorL10n.userVerificationSessionDetailsInformationUntrustedOtherUser + unstrustedInformationText = VectorL10n.userVerificationSessionDetailsAdditionalInformationUntrustedOtherUser verifyButtonTitle = VectorL10n.userVerificationSessionDetailsVerifyActionOtherUser } From 22b9b6df215ad8d9ab470d91cfb456eae5987951 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Mon, 3 Feb 2020 18:59:25 +0100 Subject: [PATCH 159/282] MXRoom: Add a method to get user encryption trust level. --- Riot/Categories/MXRoom+Riot.h | 9 +++++++++ Riot/Categories/MXRoom+Riot.m | 31 +++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/Riot/Categories/MXRoom+Riot.h b/Riot/Categories/MXRoom+Riot.h index 4da7fa074a..9d02bbc2d4 100644 --- a/Riot/Categories/MXRoom+Riot.h +++ b/Riot/Categories/MXRoom+Riot.h @@ -17,6 +17,8 @@ #import +#import "UserEncryptionTrustLevel.h" + /** Define a `MXRoom` category at Riot level. */ @@ -75,4 +77,11 @@ */ - (void)allMessages:(void (^)(void))completion; +/** + Get user encryption trust level. + + @param userId The user id. + */ +- (UserEncryptionTrustLevel)encryptionTrustLevelForUserId:(NSString*)userId; + @end diff --git a/Riot/Categories/MXRoom+Riot.m b/Riot/Categories/MXRoom+Riot.m index cc43e075a7..9347b3de85 100644 --- a/Riot/Categories/MXRoom+Riot.m +++ b/Riot/Categories/MXRoom+Riot.m @@ -324,6 +324,37 @@ - (void)allMessages:(void (^)(void))completion } } +- (UserEncryptionTrustLevel)encryptionTrustLevelForUserId:(NSString*)userId +{ + UserEncryptionTrustLevel userEncryptionTrustLevel; + + if (self.summary.isEncrypted && self.mxSession.crypto) + { + MXUsersTrustLevelSummary *usersTrustLevelSummary = [self.mxSession.crypto trustLevelSummaryForUserIds:@[userId]]; + + double trustedDevicesPercentage = usersTrustLevelSummary.trustedDevicesProgress.fractionCompleted; + + if (trustedDevicesPercentage >= 1.0) + { + userEncryptionTrustLevel = UserEncryptionTrustLevelTrusted; + } + else if (trustedDevicesPercentage == 0.0) + { + userEncryptionTrustLevel = UserEncryptionTrustLevelNormal; + } + else + { + userEncryptionTrustLevel = UserEncryptionTrustLevelWarning; + } + } + else + { + userEncryptionTrustLevel = UserEncryptionTrustLevelNone; + } + + return userEncryptionTrustLevel; +} + #pragma mark - - (MXPushRule*)getRoomPushRule From e05f3ae6afcbad0fed14169d5521d713fffbb812 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Mon, 3 Feb 2020 19:00:33 +0100 Subject: [PATCH 160/282] RoomMemberDetailsViewController: Get user encryption trust level with MXRoom. --- .../Detail/RoomMemberDetailsViewController.m | 38 +++---------------- 1 file changed, 6 insertions(+), 32 deletions(-) diff --git a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m index 3c49b1ef07..d08ef1346c 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m @@ -28,6 +28,7 @@ #import "TableViewCellWithButton.h" #import "RoomTableViewCell.h" +#import "MXRoom+Riot.h" #define TABLEVIEW_ROW_CELL_HEIGHT 46 #define TABLEVIEW_SECTION_HEADER_HEIGHT 28 @@ -482,35 +483,8 @@ - (void)refreshUserEncryptionTrustLevel return; } - if (self.mxRoom.summary.isEncrypted && self.mxRoom.mxSession.crypto) - { - MXUsersTrustLevelSummary *usersTrustLevelSummary = [self.mxRoom.mxSession.crypto trustLevelSummaryForUserIds:@[userId]]; - - double trustedDevicesPercentage = usersTrustLevelSummary.trustedDevicesProgress.fractionCompleted; - - UserEncryptionTrustLevel userEncryptionTrustLevel; - - if (trustedDevicesPercentage >= 1.0) - { - userEncryptionTrustLevel = UserEncryptionTrustLevelTrusted; - } - else if (trustedDevicesPercentage == 0.0) - { - userEncryptionTrustLevel = UserEncryptionTrustLevelNormal; - } - else - { - userEncryptionTrustLevel = UserEncryptionTrustLevelWarning; - } - - self.encryptionTrustLevel = userEncryptionTrustLevel; - [self updateMemberInfo]; - } - else - { - self.encryptionTrustLevel = UserEncryptionTrustLevelNone; - [self updateMemberInfo]; - } + self.encryptionTrustLevel = [self.mxRoom encryptionTrustLevelForUserId:userId]; + [self updateMemberInfo]; } - (UIImage*)userEncryptionBadgeImage @@ -521,13 +495,13 @@ - (UIImage*)userEncryptionBadgeImage UserEncryptionTrustLevel userEncryptionTrustLevel = self.encryptionTrustLevel; switch (userEncryptionTrustLevel) { - case RoomEncryptionTrustLevelWarning: + case UserEncryptionTrustLevelWarning: encryptionIconName = @"encryption_warning"; break; - case RoomEncryptionTrustLevelNormal: + case UserEncryptionTrustLevelNormal: encryptionIconName = @"encryption_normal"; break; - case RoomEncryptionTrustLevelTrusted: + case UserEncryptionTrustLevelTrusted: encryptionIconName = @"encryption_trusted"; break; default: From ebf089115f1f7fc9a82a407b2808ed3a92c9613a Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Mon, 3 Feb 2020 19:01:47 +0100 Subject: [PATCH 161/282] ContactTableViewCell: Handle trust level shields decoration. --- .../Contacts/Views/ContactTableViewCell.h | 1 + .../Contacts/Views/ContactTableViewCell.m | 35 +++++++++++++++++++ .../Contacts/Views/ContactTableViewCell.xib | 18 ++++++++-- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/Riot/Modules/Contacts/Views/ContactTableViewCell.h b/Riot/Modules/Contacts/Views/ContactTableViewCell.h index 32a7ff69c9..368b5386f2 100644 --- a/Riot/Modules/Contacts/Views/ContactTableViewCell.h +++ b/Riot/Modules/Contacts/Views/ContactTableViewCell.h @@ -33,6 +33,7 @@ @property (nonatomic) IBOutlet UILabel *contactDisplayNameLabel; @property (nonatomic) IBOutlet UILabel *contactInformationLabel; @property (nonatomic) IBOutlet UIView *customAccessoryView; +@property (weak, nonatomic) IBOutlet UIImageView *avatarBadgeImageView; @property (nonatomic) BOOL showCustomAccessoryView; diff --git a/Riot/Modules/Contacts/Views/ContactTableViewCell.m b/Riot/Modules/Contacts/Views/ContactTableViewCell.m index f55ce88e95..050cb95d65 100644 --- a/Riot/Modules/Contacts/Views/ContactTableViewCell.m +++ b/Riot/Modules/Contacts/Views/ContactTableViewCell.m @@ -24,6 +24,7 @@ #import "AvatarGenerator.h" #import "Tools.h" +#import "MXRoom+Riot.h" #import "NBPhoneNumberUtil.h" @@ -171,6 +172,7 @@ - (void)render:(MXKCellData *)cellData }]; [self refreshContactPresence]; + [self refreshContactBadgeImage]; } else { @@ -234,6 +236,39 @@ - (void)refreshContactThumbnail self.thumbnailView.image = image; } +- (void)refreshContactBadgeImage +{ + UserEncryptionTrustLevel userEncryptionTrustLevel = [self.mxRoom encryptionTrustLevelForUserId:contact.contactID]; + self.avatarBadgeImageView.image = [self badgeImageForUserEncryptionTrustLevel:userEncryptionTrustLevel]; +} + +- (UIImage*)badgeImageForUserEncryptionTrustLevel:(UserEncryptionTrustLevel)userEncryptionTrustLevel +{ + NSString *encryptionIconName; + UIImage *encryptionIcon; + + switch (userEncryptionTrustLevel) { + case UserEncryptionTrustLevelWarning: + encryptionIconName = @"encryption_warning"; + break; + case UserEncryptionTrustLevelNormal: + encryptionIconName = @"encryption_normal"; + break; + case UserEncryptionTrustLevelTrusted: + encryptionIconName = @"encryption_trusted"; + break; + default: + break; + } + + if (encryptionIconName) + { + encryptionIcon = [UIImage imageNamed:encryptionIconName]; + } + + return encryptionIcon; +} + - (void)refreshContactDisplayName { self.contactDisplayNameLabel.text = contact.displayName; diff --git a/Riot/Modules/Contacts/Views/ContactTableViewCell.xib b/Riot/Modules/Contacts/Views/ContactTableViewCell.xib index 2209926d91..f27b9c893e 100644 --- a/Riot/Modules/Contacts/Views/ContactTableViewCell.xib +++ b/Riot/Modules/Contacts/Views/ContactTableViewCell.xib @@ -1,11 +1,11 @@ - + - + @@ -27,6 +27,13 @@ + + + + + + + showAllEventsInRoomHistory From d8ad8b9df35f666795340bdd33133d2d2da319fe Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Thu, 20 Feb 2020 21:51:55 +0100 Subject: [PATCH 191/282] Update Matomo id --- CHANGES.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.rst b/CHANGES.rst index e2b0b20de0..1f5c9cd92d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -9,6 +9,7 @@ Improvements: * Room decoration: Use shields instead of padlocks (#2906). * Room decoration: Remove horizontal empty space when there is no decoration badge to set on room message (#2978). * RoomVC: For a room preview use room canonical alias if present when joining a room. + * Update Matomo app id (#3001) Bug fix: * Fix error when joining some public rooms, thanks to @chrismoos (PR #2888). From db9165434c7644462259001fca19048c23c614bb Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 4 Mar 2020 10:28:00 +0100 Subject: [PATCH 192/282] Update PULL_REQUEST_TEMPLATE.md --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index ce2bb7aaab..a9e8845e43 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -6,4 +6,4 @@ * [ ] Pull request is based on the develop branch * [ ] Pull request updates [CHANGES.rst](https://github.com/vector-im/riot-ios/blob/develop/CHANGES.rst) * [ ] Pull request includes screenshots or videos of UI changes -* [ ] Pull request includes a [sign off](https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst#sign-off) +* [ ] Pull request includes a [sign off](https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.md#sign-off) From 949c581a7ccf4983e9a7ea66b6fad5cf2f6a8cf5 Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 4 Mar 2020 15:05:34 +0100 Subject: [PATCH 193/282] Logs: Keep the 50 last log files iOS13 kills the app more often. We have no more one log file a day but one log file every few hours. Increase that number of files to have a wider view of what it's going on. --- Riot/AppDelegate.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 8a7808977d..ba840c89e8 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -271,7 +271,7 @@ + (void)initialize // Redirect NSLogs to files only if we are not debugging if (!isatty(STDERR_FILENO)) { - [MXLogger redirectNSLogToFiles:YES]; + [MXLogger redirectNSLogToFiles:YES numberOfFiles:50]; } NSLog(@"[AppDelegate] initialize: Done"); From e7789aa411119f9324759e986a46c85641ea8116 Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 4 Mar 2020 15:10:46 +0100 Subject: [PATCH 194/282] Push notifications: Avoid any automatic deactivation vector-im/riot-ios#3017 --- CHANGES.rst | 1 + Riot/AppDelegate.m | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 1f5c9cd92d..871e6ef869 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -14,6 +14,7 @@ Improvements: Bug fix: * Fix error when joining some public rooms, thanks to @chrismoos (PR #2888). * Fix crash due to malformed widget (#2997). + * Push notifications: Avoid any automatic deactivation (vector-im/riot-ios#3017). Changes in 0.10.4 (2019-12-11) =============================================== diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index ba840c89e8..9ae7fe0ae7 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -2125,9 +2125,13 @@ - (void)clearPushNotificationToken { NSLog(@"[AppDelegate][Push] clearPushNotificationToken: Clear existing token"); + // XXX: The following code has been commented to avoid automatic deactivation of push notifications + // There may be a race condition here where the clear happens after the update of the new push token. + // We have no evidence of this. This is a safety measure. + // Clear existing token - MXKAccountManager* accountManager = [MXKAccountManager sharedManager]; - [accountManager setPushDeviceToken:nil withPushOptions:nil]; + //MXKAccountManager* accountManager = [MXKAccountManager sharedManager]; + //[accountManager setPushDeviceToken:nil withPushOptions:nil]; } // Remove delivred notifications for a given room id except call notifications From 2c406a630c7bb906d00689d7db17edf0a778a1ee Mon Sep 17 00:00:00 2001 From: Alex Yanchenko <37913931+sucsessyan@users.noreply.github.com> Date: Thu, 5 Mar 2020 20:55:43 +0100 Subject: [PATCH 195/282] Update CONTRIBUTING.rst Change link to a right one Signed-off-by: Alexey Yanchenko --- CONTRIBUTING.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 50d20cbb40..117d3f6d24 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,4 +1,4 @@ Contributing code to Riot iOS ================================== -riot-ios follows the same pattern as https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst +riot-ios follows the same pattern as https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.md From c50215dcd1131fce87aeda73f10ce6572f95f319 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 12 Mar 2020 11:21:47 +0100 Subject: [PATCH 196/282] Security screen: Create a dedicated section for cross-signing for future debug. Reorder the screen a bit. --- Riot/Assets/en.lproj/Vector.strings | 8 +- Riot/Generated/Strings.swift | 10 +- .../Security/SecurityViewController.m | 156 ++++++++++-------- 3 files changed, 98 insertions(+), 76 deletions(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 67a3384ef6..2a3cdad8a4 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -497,7 +497,7 @@ "settings_crypto_device_name" = "Session name: "; "settings_crypto_device_id" = "\nSession ID: "; -"settings_crypto_device_key" = "\Session key:\n"; +"settings_crypto_device_key" = "\nSession key:\n"; "settings_crypto_export" = "Export keys"; "settings_crypto_blacklist_unverified_devices" = "Encrypt to verified sessions only"; @@ -560,10 +560,14 @@ "security_settings_backup" = "MESSAGE BACKUP"; +"security_settings_crosssigning" = "CROSS-SIGNING"; + +"security_settings_cryptography" = "CRYPTOGRAPHY"; +"security_settings_export_keys_manually" = "Export keys manually"; + "security_settings_advanced" = "ADVANCED"; "security_settings_blacklist_unverified_devices" = "Never send messages to untrusted sessions"; "security_settings_blacklist_unverified_devices_description" = "Verify all of a users sessions to mark them as trusted and send messages to them."; -"security_settings_export_keys_manually" = "Export keys manually"; // Manage session diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index ab61ffc81e..895d432076 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -2694,6 +2694,10 @@ internal enum VectorL10n { internal static var securitySettingsBlacklistUnverifiedDevicesDescription: String { return VectorL10n.tr("Vector", "security_settings_blacklist_unverified_devices_description") } + /// CROSS-SIGNING + internal static var securitySettingsCrosssigning: String { + return VectorL10n.tr("Vector", "security_settings_crosssigning") + } /// MY SESSIONS internal static var securitySettingsCryptoSessions: String { return VectorL10n.tr("Vector", "security_settings_crypto_sessions") @@ -2706,6 +2710,10 @@ internal enum VectorL10n { internal static var securitySettingsCryptoSessionsLoading: String { return VectorL10n.tr("Vector", "security_settings_crypto_sessions_loading") } + /// CRYPTOGRAPHY + internal static var securitySettingsCryptography: String { + return VectorL10n.tr("Vector", "security_settings_cryptography") + } /// Export keys manually internal static var securitySettingsExportKeysManually: String { return VectorL10n.tr("Vector", "security_settings_export_keys_manually") @@ -2862,7 +2870,7 @@ internal enum VectorL10n { internal static var settingsCryptoDeviceId: String { return VectorL10n.tr("Vector", "settings_crypto_device_id") } - /// Session key:\n + /// \nSession key:\n internal static var settingsCryptoDeviceKey: String { return VectorL10n.tr("Vector", "settings_crypto_device_key") } diff --git a/Riot/Modules/Settings/Security/SecurityViewController.m b/Riot/Modules/Settings/Security/SecurityViewController.m index cae1683917..269188868b 100644 --- a/Riot/Modules/Settings/Security/SecurityViewController.m +++ b/Riot/Modules/Settings/Security/SecurityViewController.m @@ -34,23 +34,28 @@ { SECTION_CRYPTO_SESSIONS, SECTION_KEYBACKUP, + SECTION_CROSSSIGNING, + SECTION_CRYPTOGRAPHY, SECTION_ADVANCED, - SECTION_DEBUG, // TODO: To remove SECTION_COUNT }; enum { - ADVANCED_BLACKLIST_UNVERIFIED_DEVICES, - ADVANCED_BLACKLIST_UNVERIFIED_DEVICES_DESCRIPTION, - ADVANCED_EXPORT, // TODO: To move to SECTION_KEYBACKUP - ADVANCED_COUNT + CROSSSIGNING_INFO, + CROSSSIGNING_BOOTSTRAP, + CROSSSIGNING_COUNT }; enum { - DEBUG_CRYPTO_INFO, - DEBUG_CROSSSIGNING_INFO, - DEBUG_CROSSSIGNING_BOOTSTRAP, - DEBUG_COUNT + CRYPTOGRAPHY_INFO, + CRYPTOGRAPHY_EXPORT, // TODO: To move to SECTION_KEYBACKUP + CRYPTOGRAPHY_COUNT +}; + +enum { + ADVANCED_BLACKLIST_UNVERIFIED_DEVICES, + ADVANCED_BLACKLIST_UNVERIFIED_DEVICES_DESCRIPTION, + ADVANCED_COUNT }; @@ -339,12 +344,6 @@ - (NSAttributedString*)crossSigningStatus // Crypto information NSMutableAttributedString *cryptoInformationString = [NSMutableAttributedString new]; - [cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc] - initWithString:@"Cross-Signing\n" - attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor, - NSFontAttributeName: [UIFont boldSystemFontOfSize:17]}]]; - - NSString *crossSigningEnabled = [NSString stringWithFormat:@"Cross-signing is %@.\n", crossSigning.isBootstrapped ? @"enabled" : myUserCrossSigningKeys ? @"enabled in read-only" : @"disabled"]; @@ -355,7 +354,7 @@ - (NSAttributedString*)crossSigningStatus NSFontAttributeName: [UIFont systemFontOfSize:17]}]]; - NSString *crossSigningKeysTrust = [NSString stringWithFormat:@"Keys are %@.\n", + NSString *crossSigningKeysTrust = [NSString stringWithFormat:@"Keys are %@.", myUserCrossSigningKeys.trustLevel.isVerified ? @"trusted" : @"not trusted"]; [cryptoInformationString appendAttributedString:[[NSMutableAttributedString alloc] @@ -462,12 +461,15 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger case SECTION_KEYBACKUP: count = keyBackupSection.numberOfRows; break; + case SECTION_CROSSSIGNING: + count = CROSSSIGNING_COUNT; + break; + case SECTION_CRYPTOGRAPHY: + count = CRYPTOGRAPHY_COUNT; + break; case SECTION_ADVANCED: count = ADVANCED_COUNT; break; - case SECTION_DEBUG: - count = DEBUG_COUNT; - break; } return count; @@ -610,107 +612,113 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N } } } - else if (section == SECTION_ADVANCED) + else if (section == SECTION_KEYBACKUP) + { + cell = [keyBackupSection cellForRowAtRow:row]; + } + else if (section == SECTION_CROSSSIGNING) { switch (row) { - case ADVANCED_BLACKLIST_UNVERIFIED_DEVICES: - { - MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; - - labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"security_settings_blacklist_unverified_devices", @"Vector", nil); - labelAndSwitchCell.mxkSwitch.on = session.crypto.globalBlacklistUnverifiedDevices; - labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; - labelAndSwitchCell.mxkSwitch.enabled = YES; - [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleBlacklistUnverifiedDevices:) forControlEvents:UIControlEventTouchUpInside]; - - cell = labelAndSwitchCell; - break; - } - case ADVANCED_BLACKLIST_UNVERIFIED_DEVICES_DESCRIPTION: + case CROSSSIGNING_INFO: { - cell = [self descriptionCellForTableView:tableView - withText:NSLocalizedStringFromTable(@"security_settings_blacklist_unverified_devices_description", @"Vector", nil) ]; - + MXKTableViewCellWithTextView *cryptoCell = [self textViewCellForTableView:tableView atIndexPath:indexPath]; + cryptoCell.mxkTextView.attributedText = [self crossSigningStatus]; + cell = cryptoCell; break; } - case ADVANCED_EXPORT: + case CROSSSIGNING_BOOTSTRAP: { MXKTableViewCellWithButton *exportKeysBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; if (!exportKeysBtnCell) { exportKeysBtnCell = [[MXKTableViewCellWithButton alloc] init]; } - else - { - exportKeysBtnCell.mxkButton.titleLabel.text = nil; - exportKeysBtnCell.mxkButton.enabled = YES; - } - - NSString *btnTitle = NSLocalizedStringFromTable(@"security_settings_export_keys_manually", @"Vector", nil); + + NSString *btnTitle = @"Bootstrap cross-signing"; [exportKeysBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; [exportKeysBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; [exportKeysBtnCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; exportKeysBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; - + [exportKeysBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; - [exportKeysBtnCell.mxkButton addTarget:self action:@selector(exportEncryptionKeys:) forControlEvents:UIControlEventTouchUpInside]; + //[exportKeysBtnCell.mxkButton addTarget:self action:@selector(bootstrapCrossSigning:) forControlEvents:UIControlEventTouchUpInside]; exportKeysBtnCell.mxkButton.accessibilityIdentifier = nil; - + + MXCrossSigning *crossSigning = self.mainSession.crypto.crossSigning; + exportKeysBtnCell.mxkButton.enabled = NO; //!crossSigning.myUserCrossSigningKeys; + cell = exportKeysBtnCell; break; } } } - else if (section == SECTION_KEYBACKUP) - { - cell = [keyBackupSection cellForRowAtRow:row]; - } - else if (section == SECTION_DEBUG) + else if (section == SECTION_CRYPTOGRAPHY) { switch (row) { - case DEBUG_CRYPTO_INFO: + case CRYPTOGRAPHY_INFO: { MXKTableViewCellWithTextView *cryptoCell = [self textViewCellForTableView:tableView atIndexPath:indexPath]; cryptoCell.mxkTextView.attributedText = [self cryptographyInformation]; cell = cryptoCell; break; } - case DEBUG_CROSSSIGNING_INFO: - { - MXKTableViewCellWithTextView *cryptoCell = [self textViewCellForTableView:tableView atIndexPath:indexPath]; - cryptoCell.mxkTextView.attributedText = [self crossSigningStatus]; - cell = cryptoCell; - break; - } - case DEBUG_CROSSSIGNING_BOOTSTRAP: + case CRYPTOGRAPHY_EXPORT: { MXKTableViewCellWithButton *exportKeysBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; if (!exportKeysBtnCell) { exportKeysBtnCell = [[MXKTableViewCellWithButton alloc] init]; } - - NSString *btnTitle = @"Bootstrap cross-signing"; + else + { + exportKeysBtnCell.mxkButton.titleLabel.text = nil; + exportKeysBtnCell.mxkButton.enabled = YES; + } + + NSString *btnTitle = NSLocalizedStringFromTable(@"security_settings_export_keys_manually", @"Vector", nil); [exportKeysBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateNormal]; [exportKeysBtnCell.mxkButton setTitle:btnTitle forState:UIControlStateHighlighted]; [exportKeysBtnCell.mxkButton setTintColor:ThemeService.shared.theme.tintColor]; exportKeysBtnCell.mxkButton.titleLabel.font = [UIFont systemFontOfSize:17]; - + [exportKeysBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; - //[exportKeysBtnCell.mxkButton addTarget:self action:@selector(bootstrapCrossSigning:) forControlEvents:UIControlEventTouchUpInside]; + [exportKeysBtnCell.mxkButton addTarget:self action:@selector(exportEncryptionKeys:) forControlEvents:UIControlEventTouchUpInside]; exportKeysBtnCell.mxkButton.accessibilityIdentifier = nil; - - MXCrossSigning *crossSigning = self.mainSession.crypto.crossSigning; - exportKeysBtnCell.mxkButton.enabled = NO; //!crossSigning.myUserCrossSigningKeys; - + cell = exportKeysBtnCell; break; } } } - + else if (section == SECTION_ADVANCED) + { + switch (row) + { + case ADVANCED_BLACKLIST_UNVERIFIED_DEVICES: + { + MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + + labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"security_settings_blacklist_unverified_devices", @"Vector", nil); + labelAndSwitchCell.mxkSwitch.on = session.crypto.globalBlacklistUnverifiedDevices; + labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + labelAndSwitchCell.mxkSwitch.enabled = YES; + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleBlacklistUnverifiedDevices:) forControlEvents:UIControlEventTouchUpInside]; + + cell = labelAndSwitchCell; + break; + } + case ADVANCED_BLACKLIST_UNVERIFIED_DEVICES_DESCRIPTION: + { + cell = [self descriptionCellForTableView:tableView + withText:NSLocalizedStringFromTable(@"security_settings_blacklist_unverified_devices_description", @"Vector", nil) ]; + + break; + } + } + } + return cell; } @@ -722,10 +730,12 @@ - (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSectio return NSLocalizedStringFromTable(@"security_settings_crypto_sessions", @"Vector", nil); case SECTION_KEYBACKUP: return NSLocalizedStringFromTable(@"security_settings_backup", @"Vector", nil); + case SECTION_CROSSSIGNING: + return NSLocalizedStringFromTable(@"security_settings_crosssigning", @"Vector", nil); + case SECTION_CRYPTOGRAPHY: + return NSLocalizedStringFromTable(@"security_settings_cryptography", @"Vector", nil); case SECTION_ADVANCED: return NSLocalizedStringFromTable(@"security_settings_advanced", @"Vector", nil); - case SECTION_DEBUG: - return @"DEBUG"; } return nil; From 9f88c2d9a6e5964906ea498b183fbe0dc9b3e4c0 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 13 Mar 2020 00:05:33 +0100 Subject: [PATCH 197/282] Create CameraAccessManager to handle camera availability and authorization. --- Riot/Modules/Camera/CameraAccessManager.swift | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 Riot/Modules/Camera/CameraAccessManager.swift diff --git a/Riot/Modules/Camera/CameraAccessManager.swift b/Riot/Modules/Camera/CameraAccessManager.swift new file mode 100644 index 0000000000..93cacaafce --- /dev/null +++ b/Riot/Modules/Camera/CameraAccessManager.swift @@ -0,0 +1,61 @@ +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// CameraAccessManager handles camera availability and authorization. +final class CameraAccessManager { + + // MARK: - Properties + + var isCameraAvailable: Bool { + return UIImagePickerController.isSourceTypeAvailable(.camera) + } + + var isCameraAccessGranted: Bool { + return AVCaptureDevice.authorizationStatus(for: .video) == .authorized + } + + // MARK: - Public + + func askAndRequestCameraAccessIfNeeded(completion: @escaping (_ granted: Bool) -> Void) { + + let authorizationStatus = AVCaptureDevice.authorizationStatus(for: .video) + + switch authorizationStatus { + case .authorized: + completion(true) + case .notDetermined: + self.requestCameraAccess(completion: { (granted) in + completion(granted) + }) + case .denied, .restricted: + completion(false) + @unknown default: + break + } + } + + // MARK: - Private + + private func requestCameraAccess(completion: @escaping (_ granted: Bool) -> Void) { + AVCaptureDevice.requestAccess(for: .video) { granted in + DispatchQueue.main.async { + completion(granted) + } + } + } +} From dce1c78f2cf1ade529b852af227ee08aa34c8a9c Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 13 Mar 2020 00:07:21 +0100 Subject: [PATCH 198/282] Create CameraAccessAlertPresenter to present common alerts related to camera access. --- .../Camera/CameraAccessAlertPresenter.swift | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 Riot/Modules/Camera/CameraAccessAlertPresenter.swift diff --git a/Riot/Modules/Camera/CameraAccessAlertPresenter.swift b/Riot/Modules/Camera/CameraAccessAlertPresenter.swift new file mode 100644 index 0000000000..721e9f596b --- /dev/null +++ b/Riot/Modules/Camera/CameraAccessAlertPresenter.swift @@ -0,0 +1,61 @@ +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +final class CameraAccessAlertPresenter { + + // MARK: - Public + + func presentPermissionDeniedAlert(from presentingViewController: UIViewController, animated: Bool) { + guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else { + return + } + + let appDisplayName = Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String ?? "" + + let alert = UIAlertController(title: VectorL10n.camera, message: VectorL10n.cameraAccessNotGranted(appDisplayName), preferredStyle: .alert) + + let cancelActionTitle = Bundle.mxk_localizedString(forKey: "ok") + let cancelAction = UIAlertAction(title: cancelActionTitle, style: .cancel, handler: { _ in + }) + + let settingsActionTitle = Bundle.mxk_localizedString(forKey: "settings") + let settingsAction = UIAlertAction(title: settingsActionTitle, style: .default, handler: { _ in + UIApplication.shared.open(settingsURL, options: [:], completionHandler: { (succeed) in + if !succeed { + print("[CameraPresenter] Fails to open settings") + } + }) + }) + + alert.addAction(cancelAction) + alert.addAction(settingsAction) + + presentingViewController.present(alert, animated: animated, completion: nil) + } + + func presentCameraUnavailableAlert(from presentingViewController: UIViewController, animated: Bool) { + + let alert = UIAlertController(title: VectorL10n.camera, message: VectorL10n.cameraUnavailable, preferredStyle: .alert) + + let okAction = UIAlertAction(title: VectorL10n.accept, style: .default, handler: nil) + + alert.addAction(okAction) + + presentingViewController.present(alert, animated: true, completion: nil) + } +} From 4ab25dbe750eef8533e922ccb3a7e6285e1a4812 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 13 Mar 2020 00:08:45 +0100 Subject: [PATCH 199/282] CameraPresenter: Use CameraAccessManager and CameraAccessAlertPresenter. --- Riot/Modules/Camera/CameraPresenter.swift | 94 +++++++---------------- 1 file changed, 26 insertions(+), 68 deletions(-) diff --git a/Riot/Modules/Camera/CameraPresenter.swift b/Riot/Modules/Camera/CameraPresenter.swift index 6ab0a6eeea..749e9abe55 100644 --- a/Riot/Modules/Camera/CameraPresenter.swift +++ b/Riot/Modules/Camera/CameraPresenter.swift @@ -35,16 +35,27 @@ import AVFoundation // MARK: - Properties - // MARK: - Private + // MARK: Private + + private let cameraAccessManager: CameraAccessManager + private let cameraAccessAlertPresenter: CameraAccessAlertPresenter private weak var presentingViewController: UIViewController? private weak var cameraViewController: UIViewController? private var mediaUTIs: [MXKUTI] = [] - // MARK: - Public + // MARK: Public @objc weak var delegate: CameraPresenterDelegate? + // MARK: - Setup + + override init() { + self.cameraAccessManager = CameraAccessManager() + self.cameraAccessAlertPresenter = CameraAccessAlertPresenter() + super.init() + } + // MARK: - Public @objc func presentCamera(from presentingViewController: UIViewController, with mediaUTIs: [MXKUTI], animated: Bool) { @@ -63,24 +74,21 @@ import AVFoundation // MARK: - Private private func checkCameraPermissionAndPresentCamera(animated: Bool) { + guard let presentingViewController = self.presentingViewController else { + return + } - let authorizationStatus = AVCaptureDevice.authorizationStatus(for: .video) + guard self.cameraAccessManager.isCameraAvailable else { + self.cameraAccessAlertPresenter.presentCameraUnavailableAlert(from: presentingViewController, animated: animated) + return + } - switch authorizationStatus { - case .authorized: - self.presentCameraController(animated: animated) - case .notDetermined: - self.requestCameraAccess(completion: { (granted) in - if granted { - self.presentCameraController(animated: animated) - } else { - self.presentPermissionDeniedAlert() - } - }) - case .denied, .restricted: - self.presentPermissionDeniedAlert() - @unknown default: - break + self.cameraAccessManager.askAndRequestCameraAccessIfNeeded { (granted) in + if granted { + self.presentCameraController(animated: animated) + } else { + self.cameraAccessAlertPresenter.presentPermissionDeniedAlert(from: presentingViewController, animated: animated) + } } } @@ -114,56 +122,6 @@ import AVFoundation return imagePickerController } - - private func requestCameraAccess(completion: @escaping (_ granted: Bool) -> Void) { - AVCaptureDevice.requestAccess(for: .video) { granted in - DispatchQueue.main.async { - completion(granted) - } - } - } - - private func presentPermissionDeniedAlert() { - guard let presentingViewController = self.presentingViewController, let settingsURL = URL(string: UIApplication.openSettingsURLString) else { - return - } - - let appDisplayName = Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String ?? "" - - let alert = UIAlertController(title: VectorL10n.camera, message: VectorL10n.cameraAccessNotGranted(appDisplayName), preferredStyle: .alert) - - let cancelActionTitle = Bundle.mxk_localizedString(forKey: "ok") - let cancelAction = UIAlertAction(title: cancelActionTitle, style: .cancel, handler: { _ in - }) - - let settingsActionTitle = Bundle.mxk_localizedString(forKey: "settings") - let settingsAction = UIAlertAction(title: settingsActionTitle, style: .default, handler: { _ in - UIApplication.shared.open(settingsURL, options: [:], completionHandler: { (succeed) in - if !succeed { - print("[CameraPresenter] Fails to open settings") - } - }) - }) - - alert.addAction(cancelAction) - alert.addAction(settingsAction) - - presentingViewController.present(alert, animated: true, completion: nil) - } - - private func presentCameraUnavailableAlert() { - guard let presentingViewController = self.presentingViewController else { - return - } - - let alert = UIAlertController(title: VectorL10n.camera, message: VectorL10n.cameraUnavailable, preferredStyle: .alert) - - let okAction = UIAlertAction(title: VectorL10n.accept, style: .default, handler: nil) - - alert.addAction(okAction) - - presentingViewController.present(alert, animated: true, completion: nil) - } } // MARK: - UIImagePickerControllerDelegate From 1def653460b97e4ef1ecb33f47fded5c3b004a12 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 13 Mar 2020 00:11:00 +0100 Subject: [PATCH 200/282] Create a common close button. --- .../Common/Buttons/Close/CloseButton.swift | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 Riot/Modules/Common/Buttons/Close/CloseButton.swift diff --git a/Riot/Modules/Common/Buttons/Close/CloseButton.swift b/Riot/Modules/Common/Buttons/Close/CloseButton.swift new file mode 100644 index 0000000000..a46a0a9f2d --- /dev/null +++ b/Riot/Modules/Common/Buttons/Close/CloseButton.swift @@ -0,0 +1,95 @@ +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +final class CloseButton: UIButton, Themable { + + // MARK: - Constants + + private enum CircleBackgroundConstants { + static let height: CGFloat = 30.0 + static let highlightedAlha: CGFloat = 0.5 + static let normalAlha: CGFloat = 1.0 + } + + // MARK: - Properties + + // MARK: Private + + private var theme: Theme? + + private var circleBackgroundView: UIView! + + // MARK: Public + + override var isHighlighted: Bool { + didSet { + self.circleBackgroundView.alpha = self.isHighlighted ? CircleBackgroundConstants.highlightedAlha : CircleBackgroundConstants.normalAlha + } + } + + // MARK: - Life cycle + + override func awakeFromNib() { + super.awakeFromNib() + + self.backgroundColor = UIColor.clear + self.setImage(Asset.Images.closeButton.image, for: .normal) + self.setupCircleView() + + self.update(theme: ThemeService.shared().theme) + } + + override func layoutSubviews() { + super.layoutSubviews() + + self.sendSubviewToBack(self.circleBackgroundView) + self.circleBackgroundView.layer.cornerRadius = self.circleBackgroundView.bounds.height/2 + } + + // MARK: - Private + + private func setupCircleView() { + + let rect = CGRect(x: 0, y: 0, width: CircleBackgroundConstants.height, height: CircleBackgroundConstants.height) + let view = UIView(frame: rect) + view.translatesAutoresizingMaskIntoConstraints = false + view.isUserInteractionEnabled = false + view.layer.masksToBounds = true + + self.addSubview(view) + + NSLayoutConstraint.activate([ + view.heightAnchor.constraint(equalToConstant: CircleBackgroundConstants.height), + view.widthAnchor.constraint(equalTo: view.heightAnchor, multiplier: 1.0), + view.centerXAnchor.constraint(equalTo: self.centerXAnchor), + view.centerYAnchor.constraint(equalTo: self.centerYAnchor) + ]) + + self.sendSubviewToBack(view) + + self.circleBackgroundView = view + } + + // MARK: - Themable + + func update(theme: Theme) { + self.theme = theme + + self.circleBackgroundView.backgroundColor = theme.headerTextSecondaryColor + } +} From 5a740dbac9e53b0d94ab63d6d5b989db3f035579 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 13 Mar 2020 00:14:24 +0100 Subject: [PATCH 201/282] Create QRCodeGenerator to generate QR image from data. --- .../Scanning/QRCode/QRCodeGenerator.swift | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 Riot/Modules/DeviceVerification/Verify/Scanning/QRCode/QRCodeGenerator.swift diff --git a/Riot/Modules/DeviceVerification/Verify/Scanning/QRCode/QRCodeGenerator.swift b/Riot/Modules/DeviceVerification/Verify/Scanning/QRCode/QRCodeGenerator.swift new file mode 100644 index 0000000000..f3ad32c054 --- /dev/null +++ b/Riot/Modules/DeviceVerification/Verify/Scanning/QRCode/QRCodeGenerator.swift @@ -0,0 +1,50 @@ +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +final class QRCodeGenerator { + + // MARK: - Constants + + private enum Constants { + static let qrCodeGeneratorFilter = "CIQRCodeGenerator" + static let qrCodeInputCorrectionLevel = "M" + } + + // MARK: - Public + + func generateCode(from data: Data, with size: CGSize) -> UIImage? { + guard let filter = CIFilter(name: Constants.qrCodeGeneratorFilter) else { + return nil + } + + filter.setValue(data, forKey: "inputMessage") + filter.setValue(Constants.qrCodeInputCorrectionLevel, forKey: "inputCorrectionLevel") // Be sure to use same error resilience level as other platform + + guard let ciImage = filter.outputImage else { + return nil + } + + let scaleX = size.width/ciImage.extent.size.width + let scaleY = size.height/ciImage.extent.size.height + + let transform = CGAffineTransform(scaleX: scaleX, y: scaleY) + + let transformedCIImage = ciImage.transformed(by: transform) + return UIImage(ciImage: transformedCIImage) + } +} From 40e393a11e47a4a7852691b1b55682c0206e0884 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 13 Mar 2020 00:16:49 +0100 Subject: [PATCH 202/282] Create QRCodeReaderViewController used to scan a QR code. --- .../QRCodeReaderViewController.storyboard | 62 ++++ .../Reader/QRCodeReaderViewController.swift | 270 ++++++++++++++++++ 2 files changed, 332 insertions(+) create mode 100644 Riot/Modules/DeviceVerification/Verify/Scanning/QRCode/Reader/QRCodeReaderViewController.storyboard create mode 100644 Riot/Modules/DeviceVerification/Verify/Scanning/QRCode/Reader/QRCodeReaderViewController.swift diff --git a/Riot/Modules/DeviceVerification/Verify/Scanning/QRCode/Reader/QRCodeReaderViewController.storyboard b/Riot/Modules/DeviceVerification/Verify/Scanning/QRCode/Reader/QRCodeReaderViewController.storyboard new file mode 100644 index 0000000000..8a084200ab --- /dev/null +++ b/Riot/Modules/DeviceVerification/Verify/Scanning/QRCode/Reader/QRCodeReaderViewController.storyboard @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/DeviceVerification/Verify/Scanning/QRCode/Reader/QRCodeReaderViewController.swift b/Riot/Modules/DeviceVerification/Verify/Scanning/QRCode/Reader/QRCodeReaderViewController.swift new file mode 100644 index 0000000000..47f032ef0f --- /dev/null +++ b/Riot/Modules/DeviceVerification/Verify/Scanning/QRCode/Reader/QRCodeReaderViewController.swift @@ -0,0 +1,270 @@ +/* + Copyright 2020 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 ZXingObjC + +protocol QRCodeReaderViewControllerDelegate: class { + func qrCodeReaderViewController(_ viewController: QRCodeReaderViewController, didFound payloadData: Data) + func qrCodeReaderViewControllerDidCancel(_ viewController: QRCodeReaderViewController) +} + +/// QRCodeReaderViewController is a view controller used to scan a QR code +/// Some methods are based on [ZXing sample](https://github.com/zxingify/zxingify-objc/blob/master/examples/BarcodeScannerSwift/BarcodeScannerSwift/ViewController.swift) +final class QRCodeReaderViewController: UIViewController { + + // MARK: - Constants + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var closeButton: CloseButton! + @IBOutlet private weak var codeReaderContainerView: UIView! + + // MARK: Private + + private var theme: Theme! + private var errorPresenter: MXKErrorPresentation! + + private lazy var zxCapture: ZXCapture = ZXCapture() + private var captureSizeTransform: CGAffineTransform? + private var isScanning: Bool = false + private var isFirstApplyOrientation: Bool = false + + // MARK: Public + + weak var delegate: QRCodeReaderViewControllerDelegate? + + // MARK: - Setup + + class func instantiate() -> QRCodeReaderViewController { + let viewController = StoryboardScene.QRCodeReaderViewController.initialScene.instantiate() + viewController.theme = ThemeService.shared().theme + return viewController + } + + deinit { + self.stopScanning() + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + self.setupViews() + self.errorPresenter = MXKErrorAlertPresentation() + + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + self.startScanning() + } + + override public func viewWillDisappear(_ animated: Bool) { + self.stopScanning() + + super.viewWillDisappear(animated) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.theme.statusBarStyle + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + guard isFirstApplyOrientation == false else { + return + } + + isFirstApplyOrientation = true + applyOrientation() + } + + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + super.viewWillTransition(to: size, with: coordinator) + + coordinator.animate(alongsideTransition: { (context) in + // do nothing + }, completion: { [weak self] (context) in + guard let self = self else { + return + } + self.applyOrientation() + }) + } + + // MARK: - Public + + func startScanning() { + self.zxCapture.start() + isScanning = true + } + + func stopScanning() { + self.zxCapture.stop() + isScanning = false + } + + // MARK: - Private + + private func update(theme: Theme) { + self.theme = theme + + self.closeButton.update(theme: theme) + } + + 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.setupQRCodeReaderView() + } + + private func setupQRCodeReaderView() { + zxCapture.delegate = self + zxCapture.camera = zxCapture.back() + + zxCapture.layer.frame = codeReaderContainerView.bounds + codeReaderContainerView.layer.addSublayer(zxCapture.layer) + } + + private func applyOrientation() { + + let orientation = UIApplication.shared.statusBarOrientation + let captureRotation: Double + let scanRectRotation: Double + + switch orientation { + case .portrait: + captureRotation = 0 + scanRectRotation = 90 + case .landscapeLeft: + captureRotation = 90 + scanRectRotation = 180 + case .landscapeRight: + captureRotation = 270 + scanRectRotation = 0 + case .portraitUpsideDown: + captureRotation = 180 + scanRectRotation = 270 + default: + captureRotation = 0 + scanRectRotation = 90 + } + + applyRectOfInterest(orientation: orientation) + + let angleRadius = captureRotation / 180.0 * Double.pi + let captureTranform = CGAffineTransform(rotationAngle: CGFloat(angleRadius)) + + zxCapture.transform = captureTranform + zxCapture.rotation = CGFloat(scanRectRotation) + zxCapture.layer.frame = codeReaderContainerView.frame + } + + private func applyRectOfInterest(orientation: UIInterfaceOrientation) { + guard var transformedVideoRect = codeReaderContainerView?.frame, + let cameraSessionPreset = zxCapture.sessionPreset + else { return } + + var scaleVideoX, scaleVideoY: CGFloat + var videoHeight, videoWidth: CGFloat + + // Currently support only for 1920x1080 || 1280x720 + if cameraSessionPreset == AVCaptureSession.Preset.hd1920x1080.rawValue { + videoHeight = 1080.0 + videoWidth = 1920.0 + } else { + videoHeight = 720.0 + videoWidth = 1280.0 + } + + if orientation == UIInterfaceOrientation.portrait { + scaleVideoX = self.view.frame.width / videoHeight + scaleVideoY = self.view.frame.height / videoWidth + + // Convert CGPoint under portrait mode to map with orientation of image + // because the image will be cropped before rotate + // reference: https://github.com/TheLevelUp/ZXingObjC/issues/222 + let realX = transformedVideoRect.origin.y + let realY = self.view.frame.size.width - transformedVideoRect.size.width - transformedVideoRect.origin.x + let realWidth = transformedVideoRect.size.height + let realHeight = transformedVideoRect.size.width + transformedVideoRect = CGRect(x: realX, y: realY, width: realWidth, height: realHeight) + + } else { + scaleVideoX = self.view.frame.width / videoWidth + scaleVideoY = self.view.frame.height / videoHeight + } + + captureSizeTransform = CGAffineTransform(scaleX: 1.0/scaleVideoX, y: 1.0/scaleVideoY) + + guard let _captureSizeTransform = captureSizeTransform else { + return + } + + let transformRect = transformedVideoRect.applying(_captureSizeTransform) + zxCapture.scanRect = transformRect + } + + // MARK: - Actions + + @IBAction private func closeButtonAction(_ sender: Any) { + self.delegate?.qrCodeReaderViewControllerDidCancel(self) + } +} + +// MARK: - ZXCaptureDelegate +extension QRCodeReaderViewController: ZXCaptureDelegate { + + func captureCameraIsReady(_ capture: ZXCapture!) { + isScanning = true + } + + func captureResult(_ capture: ZXCapture!, result: ZXResult!) { + guard let zxResult = result, isScanning == true else { + return + } + + guard zxResult.barcodeFormat == kBarcodeFormatQRCode else { + return + } + + self.stopScanning() + + if let bytes = result.resultMetadata.object(forKey: kResultMetadataTypeByteSegments.rawValue) as? NSArray, + let byteArray = bytes.firstObject as? ZXByteArray { + + let data = Data(bytes: UnsafeRawPointer(byteArray.array), count: Int(byteArray.length)) + + self.delegate?.qrCodeReaderViewController(self, didFound: data) + } + } +} From c3c047c6cadbf080622faf89e5c3a47d8dd344b9 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 13 Mar 2020 18:41:10 +0100 Subject: [PATCH 203/282] Add camera button assets. --- .../Common/camera.imageset/Contents.json | 23 ++++++++++++++++++ .../Common/camera.imageset/camera.png | Bin 0 -> 574 bytes .../Common/camera.imageset/camera@2x.png | Bin 0 -> 1038 bytes .../Common/camera.imageset/camera@3x.png | Bin 0 -> 1567 bytes Riot/Generated/Images.swift | 1 + 5 files changed, 24 insertions(+) create mode 100644 Riot/Assets/Images.xcassets/Common/camera.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/Common/camera.imageset/camera.png create mode 100644 Riot/Assets/Images.xcassets/Common/camera.imageset/camera@2x.png create mode 100644 Riot/Assets/Images.xcassets/Common/camera.imageset/camera@3x.png diff --git a/Riot/Assets/Images.xcassets/Common/camera.imageset/Contents.json b/Riot/Assets/Images.xcassets/Common/camera.imageset/Contents.json new file mode 100644 index 0000000000..d21aeca7bd --- /dev/null +++ b/Riot/Assets/Images.xcassets/Common/camera.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "camera.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "camera@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "camera@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Riot/Assets/Images.xcassets/Common/camera.imageset/camera.png b/Riot/Assets/Images.xcassets/Common/camera.imageset/camera.png new file mode 100644 index 0000000000000000000000000000000000000000..541f819215f64d6368559655617c29943404601a GIT binary patch literal 574 zcmV-E0>S->P)ZO1H%Cv|QB@2dzdUGALA&zA= z$YD7eu&M(FcDfRog?B=v3lZhuq3x$bqq`DRoV+wcbrol9g%U?&1kTo&e>R8_E+zc>;?kKs-Ts1LIpRfkDn8H#Voh zDatV}d)nPAz`Ki#cV^ZY+k90N+Sbg{J^OD@PXpX>$EOV;kzVi77KkVEaRp)IAU~ct zw}%9h0{F_>>~l6p2zUO1`E&9Q|8g!zBnAoGgt34&d&b%9T7xHXqj{veDLh}Xrsye?)t+g;`pFsik4E8gsw`5=N;l7o6bKUJ1?1{ zrF8iOGHX}vd$Y|%0|E~a0^#SGJDh|}=~+7ho5u*lfoU?E{jvA@PvcS&Pvm2UKtiNb zXNi=Wl8fXvRC0xcVx13_PMsxEYD%%-R#emJcKbsj3=s^^MQ$RUqXR^8dU`tl^1EFuJ^0WU|f{6Eu_QQ_ar5atz9ev0u(0FmCZ{$n!y8=BQcSrJID{|j0mIe@|u!U&-d#$5>Kd9b}))7g{OPrN2WMv&1@~3kL*WD|G);6;DMM?m9`QjUE2WZIG zl8&ktZv6D>L~BO8{}MBvRt?zJlil0q=@=@N#0xKqG9-%V z0F3cd)bhEWlCBG)baXAjc}jOKIsn}{nSA{?sSciW*ZaHHes`&JYrh?6&2-+1$tp95 z$4DUgJRpuuR6dA7Y9{Gsj6t;yJZw8Up<84W~!O>skfe5Hi8E^td( zw#OK44wVfrrQzCSN4;sZW%C7_S|flRNgkJO7h35M+&JWiMoNN7v-Ee{Sd<_}OM=BY z^+O7RFV5E0e=$jmV@OO=7Ldg)q-f?6JvH!?+tz%(mE8VAtlWRI=i2wiq07*qo IM6N<$f}UdH>Hq)$ literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images.xcassets/Common/camera.imageset/camera@3x.png b/Riot/Assets/Images.xcassets/Common/camera.imageset/camera@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..3458fb8d879a7b434fb71aea727ba6baf5f1b272 GIT binary patch literal 1567 zcmV+)2H^RLP)wnZD$n6hj0(RiK3RtuI)m_)x5jdG}q#%(YvoWLTVuduX2V}T+ORMscRu_ zxko8Mw0lqxV{BKh%g}- zzFg}z(_F0p5?}_R#r1vUAF-UJ%w&D#O7B4G#y6gbFC#LqCC6=$u0ei*wqA63FHw;^>K9AF?byvEEqg<%VX2 zJp*=ME6@4R@Kh66fDVv(=^B^df{o_-K+biH?y?2!!zTb+V3$9yYmu4A92B|U#Fo>8 zPXvyLd4>>*1(9oHG(1(U6@_fx0W3wA*0v48GsqUClR4y^0AnH=E*U&=E#Q2Ur^6DI zj0p4Cnr)6gI~HtU+>arU^%$oPElae`zkI^y_b!Y*e5EvHh6L8T)$| z=37>T@*Y`^@HMueL`oAVt4)*mdXZ4fLQ$_S*D8rp1ZpH>-7 zkgPR4!&6tM3`yuqE{ty%FiWN2IUwFJ2iTVCGi9BW|_`=XodON zyBd^8vN@}c%&Xnd67qF0%sJ(pORcL#SNnN7s9Dg8H4<$?_b9~%Y3(jWcBur&x$>Tz zQvz#^nic7}80O78Bp-Nlo+mEn%6oF_KU_GIyb2Uh5lAWokYhdbiZfwG{U^B0NtueG zA`oIuRg&naNbyWuQYLlRn6ah}3aAJ~h)w5!kGhjZcL5IR87QD*K|nbb0vJL;YBhV} z9>EY4(3+&36DGS?3aoK1ylq|Q;8{kJd&NyBT`z&p4ya!YP+{; zVg70>B`30AWXTMk+##w-D-Mg}-n&cTZKM`dckbMXv80Y$p0LH zbm&W*X_S&`j7Ecw*EW^Za`>7hP+Be~mL!GvVqIa42rSQ zSnEMvmn*^UzsB~G1?h6-9B!`2`w#pVd?wv)?gq}R%hmH2Lp~~aVcSj>uQ%6@rP>D- zH=wi8aPU<+N!PMH?}c!qa-hF?)&8BlN(GX7$|c%Os6>K^IFlOFb(sI1kH~jE6~1D$ zC=hQQ9kV#=H+O)YvwoH%VVg&rQXYke2Z1*zAwX#Z{894{6jW4HR8&-y&;JT+X-EGF R761SM002ovPDHLkV1mav(u)89 literal 0 HcmV?d00001 diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index f31988bd7d..7f4d0c7e75 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -31,6 +31,7 @@ internal enum Asset { internal static let riotIconCallkit = ImageAsset(name: "riot_icon_callkit") internal static let adminIcon = ImageAsset(name: "admin_icon") internal static let backIcon = ImageAsset(name: "back_icon") + internal static let camera = ImageAsset(name: "camera") internal static let chevron = ImageAsset(name: "chevron") internal static let closeButton = ImageAsset(name: "close_button") internal static let disclosureIcon = ImageAsset(name: "disclosure_icon") From d9d753775b0e279694ab9285aaadbeaed982a85d Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 13 Mar 2020 18:45:31 +0100 Subject: [PATCH 204/282] QR code verification: Add localizations. --- Riot/Assets/en.lproj/Vector.strings | 15 ++++++++++++ Riot/Generated/Strings.swift | 36 +++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 67a3384ef6..b8a72492c4 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -1140,6 +1140,9 @@ // MARK: - Key Verification +"key_verification_bootstrap_not_setup_title" = "Error"; +"key_verification_bootstrap_not_setup_message" = "You have to bootstrap cross-signing"; + // Tiles "key_verification_tile_request_incoming_title" = "Verification request"; @@ -1162,6 +1165,18 @@ "key_verification_incoming_request_incoming_alert_message" = "%@ wants to verify"; +// MARK: QR code + +"key_verification_verify_qr_code_title" = "Verify by scanning"; +"key_verification_verify_qr_code_information" = "Scan the code to securely verify each other."; +"key_verification_verify_qr_code_scan_code_action" = "Scan their code"; +"key_verification_verify_qr_code_cannot_scan_action" = "Can't scan?"; + +"key_verification_verify_qr_code_other_scan_my_code_title" = "Did the other user successfully scan the QR code?"; + +"key_verification_verify_qr_code_scan_other_code_success_title" = "Code validated!"; +"key_verification_verify_qr_code_scan_other_code_success_message" = "QR code has been successfully validated."; + // MARK: - User verification // Start diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index ab61ffc81e..c8c9d9b572 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -1470,6 +1470,14 @@ internal enum VectorL10n { internal static var keyBackupSetupTitle: String { return VectorL10n.tr("Vector", "key_backup_setup_title") } + /// You have to bootstrap cross-signing + internal static var keyVerificationBootstrapNotSetupMessage: String { + return VectorL10n.tr("Vector", "key_verification_bootstrap_not_setup_message") + } + /// Error + internal static var keyVerificationBootstrapNotSetupTitle: String { + return VectorL10n.tr("Vector", "key_verification_bootstrap_not_setup_title") + } /// %@ wants to verify internal static func keyVerificationIncomingRequestIncomingAlertMessage(_ p1: String) -> String { return VectorL10n.tr("Vector", "key_verification_incoming_request_incoming_alert_message", p1) @@ -1534,6 +1542,34 @@ internal enum VectorL10n { internal static var keyVerificationVerifiedUserDescription2: String { return VectorL10n.tr("Vector", "key_verification_verified_user_description_2") } + /// Can't scan? + internal static var keyVerificationVerifyQrCodeCannotScanAction: String { + return VectorL10n.tr("Vector", "key_verification_verify_qr_code_cannot_scan_action") + } + /// Scan the code to securely verify each other. + internal static var keyVerificationVerifyQrCodeInformation: String { + return VectorL10n.tr("Vector", "key_verification_verify_qr_code_information") + } + /// Did the other user successfully scan the QR code? + internal static var keyVerificationVerifyQrCodeOtherScanMyCodeTitle: String { + return VectorL10n.tr("Vector", "key_verification_verify_qr_code_other_scan_my_code_title") + } + /// Scan their code + internal static var keyVerificationVerifyQrCodeScanCodeAction: String { + return VectorL10n.tr("Vector", "key_verification_verify_qr_code_scan_code_action") + } + /// QR code has been successfully validated. + internal static var keyVerificationVerifyQrCodeScanOtherCodeSuccessMessage: String { + return VectorL10n.tr("Vector", "key_verification_verify_qr_code_scan_other_code_success_message") + } + /// Code validated! + internal static var keyVerificationVerifyQrCodeScanOtherCodeSuccessTitle: String { + return VectorL10n.tr("Vector", "key_verification_verify_qr_code_scan_other_code_success_title") + } + /// Verify by scanning + internal static var keyVerificationVerifyQrCodeTitle: String { + return VectorL10n.tr("Vector", "key_verification_verify_qr_code_title") + } /// Verify this user by confirming the following unique emoji appears on their screen, in the same order. internal static var keyVerificationVerifyUserTitleEmoji: String { return VectorL10n.tr("Vector", "key_verification_verify_user_title_emoji") From cb2c7e8655347458dce3bb14e7127b2f6bf477eb Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 13 Mar 2020 18:46:45 +0100 Subject: [PATCH 205/282] QR code verification: Add scanning verification screen. --- Riot/Generated/Storyboards.swift | 10 + ...nVerifyByScanningViewController.storyboard | 232 +++++++++++++ ...cationVerifyByScanningViewController.swift | 321 ++++++++++++++++++ 3 files changed, 563 insertions(+) create mode 100644 Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewController.storyboard create mode 100644 Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewController.swift diff --git a/Riot/Generated/Storyboards.swift b/Riot/Generated/Storyboards.swift index 66ef12a4b3..d5db2dc653 100644 --- a/Riot/Generated/Storyboards.swift +++ b/Riot/Generated/Storyboards.swift @@ -82,6 +82,16 @@ internal enum StoryboardScene { internal static let initialScene = InitialSceneType(storyboard: KeyBackupSetupSuccessFromRecoveryKeyViewController.self) } + internal enum KeyVerificationVerifyByScanningViewController: StoryboardType { + internal static let storyboardName = "KeyVerificationVerifyByScanningViewController" + + internal static let initialScene = InitialSceneType(storyboard: KeyVerificationVerifyByScanningViewController.self) + } + internal enum QRCodeReaderViewController: StoryboardType { + internal static let storyboardName = "QRCodeReaderViewController" + + internal static let initialScene = InitialSceneType(storyboard: QRCodeReaderViewController.self) + } internal enum ReactionHistoryViewController: StoryboardType { internal static let storyboardName = "ReactionHistoryViewController" diff --git a/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewController.storyboard b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewController.storyboard new file mode 100644 index 0000000000..73ef5980a2 --- /dev/null +++ b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewController.storyboard @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewController.swift b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewController.swift new file mode 100644 index 0000000000..43b575859a --- /dev/null +++ b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewController.swift @@ -0,0 +1,321 @@ +// File created from ScreenTemplate +// $ createScreen.sh Verify KeyVerificationVerifyByScanning +/* + Copyright 2020 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 KeyVerificationVerifyByScanningViewController: UIViewController { + + // MARK: - Constants + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var scrollView: UIScrollView! + + @IBOutlet private weak var closeButton: UIButton! + + @IBOutlet private weak var titleView: UIView! + + @IBOutlet private weak var titleLabel: UILabel! + @IBOutlet private weak var informationLabel: UILabel! + + @IBOutlet private weak var codeImageView: UIImageView! + + @IBOutlet private weak var scanCodeButton: UIButton! + @IBOutlet private weak var cannotScanButton: UIButton! + + @IBOutlet weak var qrCodeContainerView: UIView! + + @IBOutlet weak var scanButtonContainerView: UIView! + + // MARK: Private + + private var viewModel: KeyVerificationVerifyByScanningViewModelType! + private var theme: Theme! + private var errorPresenter: MXKErrorPresentation! + private var activityPresenter: ActivityIndicatorPresenter! + private var cameraAccessAlertPresenter: CameraAccessAlertPresenter! + private var cameraAccessManager: CameraAccessManager! + + private weak var qrCodeReaderViewController: QRCodeReaderViewController! + + // MARK: - Setup + + class func instantiate(with viewModel: KeyVerificationVerifyByScanningViewModelType) -> KeyVerificationVerifyByScanningViewController { + let viewController = StoryboardScene.KeyVerificationVerifyByScanningViewController.initialScene.instantiate() + viewController.viewModel = viewModel + viewController.theme = ThemeService.shared().theme + return viewController + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + self.setupViews() + self.activityPresenter = ActivityIndicatorPresenter() + self.errorPresenter = MXKErrorAlertPresentation() + self.cameraAccessAlertPresenter = CameraAccessAlertPresenter() + self.cameraAccessManager = CameraAccessManager() + + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + + self.viewModel.viewDelegate = self + + self.viewModel.process(viewAction: .loadData) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + // Hide back button + self.navigationItem.setHidesBackButton(true, animated: animated) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.theme.statusBarStyle + } + + // MARK: - Private + + private func update(theme: Theme) { + self.theme = theme + + self.view.backgroundColor = theme.headerBackgroundColor + + if let navigationBar = self.navigationController?.navigationBar { + theme.applyStyle(onNavigationBar: navigationBar) + } + + self.titleLabel.textColor = theme.textPrimaryColor + self.informationLabel.textColor = theme.textPrimaryColor + + if let themableCloseButton = self.closeButton as? Themable { + themableCloseButton.update(theme: theme) + } + + theme.applyStyle(onButton: self.scanCodeButton) + theme.applyStyle(onButton: self.cannotScanButton) + } + + 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() { + let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in + self?.cancelButtonAction() + } + + self.titleView.isHidden = self.navigationController != nil + + self.navigationItem.rightBarButtonItem = cancelBarButtonItem + + self.title = VectorL10n.keyVerificationVerifyQrCodeTitle + self.titleLabel.text = VectorL10n.keyVerificationVerifyQrCodeTitle + self.informationLabel.text = VectorL10n.keyVerificationVerifyQrCodeInformation + + self.scanCodeButton.setTitle(VectorL10n.keyVerificationVerifyQrCodeScanCodeAction, for: .normal) + self.cannotScanButton.setTitle(VectorL10n.keyVerificationVerifyQrCodeCannotScanAction, for: .normal) + } + + private func render(viewState: KeyVerificationVerifyByScanningViewState) { + switch viewState { + case .loading: + self.renderLoading() + case .loaded(viewData: let viewData): + self.renderLoaded(viewData: viewData) + case .error(let error): + self.render(error: error) + case .scannedCodeValidated(let isValid): + self.renderScannedCode(valid: isValid) + case .otherUserScannedMyCode: + self.renderOtherScannedMyCode() + case .cancelled(let reason): + self.renderCancelled(reason: reason) + case .cancelledByMe(let reason): + self.renderCancelledByMe(reason: reason) + } + } + + private func renderLoading() { + self.activityPresenter.presentActivityIndicator(on: self.view, animated: true) + } + + private func renderLoaded(viewData: KeyVerificationVerifyByScanningViewData) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + + let hideQRCodeImage: Bool + + if let qrCodePayloadData = viewData.qrCodeData { + hideQRCodeImage = false + self.codeImageView.image = self.qrCodeImage(from: qrCodePayloadData) + } else { + hideQRCodeImage = true + } + + self.qrCodeContainerView.isHidden = hideQRCodeImage + self.scanButtonContainerView.isHidden = !viewData.showScanAction + } + + private func render(error: Error) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil) + } + + private func qrCodeImage(from data: Data) -> UIImage? { + let codeGenerator = QRCodeGenerator() + return codeGenerator.generateCode(from: data, with: self.codeImageView.frame.size) + } + + private func presentQRCodeReader(animated: Bool) { + let qrCodeViewController = QRCodeReaderViewController.instantiate() + qrCodeViewController.delegate = self + self.present(qrCodeViewController, animated: animated, completion: nil) + self.qrCodeReaderViewController = qrCodeViewController + } + + private func renderScannedCode(valid: Bool) { + if valid { + self.qrCodeReaderViewController.view.isUserInteractionEnabled = false + self.qrCodeReaderViewController.stopScanning() + self.presentCodeValidated(animated: true) { + self.dismiss(animated: true, completion: { + self.viewModel.process(viewAction: .acknowledgeMyUserScannedOtherCode) + }) + } + } + } + + private func renderOtherScannedMyCode() { + let alert = UIAlertController(title: VectorL10n.keyVerificationVerifyQrCodeOtherScanMyCodeTitle, + message: nil, + preferredStyle: .alert) + + let okAction = UIAlertAction(title: Bundle.mxk_localizedString(forKey: "yes"), style: .default, handler: { _ in + self.viewModel.process(viewAction: .acknowledgeOtherScannedMyCode(true)) + }) + alert.addAction(okAction) + + let cancelAction = UIAlertAction(title: Bundle.mxk_localizedString(forKey: "no"), style: .cancel, handler: { _ in + self.viewModel.process(viewAction: .acknowledgeOtherScannedMyCode(false)) + }) + alert.addAction(cancelAction) + + self.present(alert, animated: true, completion: nil) + } + + private func renderCancelled(reason: MXTransactionCancelCode) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + + self.errorPresenter.presentError(from: self, title: "", message: VectorL10n.deviceVerificationCancelled, animated: true) { + self.viewModel.process(viewAction: .cancel) + } + } + + private func renderCancelledByMe(reason: MXTransactionCancelCode) { + if reason.value != MXTransactionCancelCode.user().value { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + + self.errorPresenter.presentError(from: self, title: "", message: VectorL10n.deviceVerificationCancelledByMe(reason.humanReadable), animated: true) { + self.viewModel.process(viewAction: .cancel) + } + } else { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + } + } + + private func presentCodeValidated(animated: Bool, completion: @escaping (() -> Void)) { + + let alert = UIAlertController(title: VectorL10n.keyVerificationVerifyQrCodeScanOtherCodeSuccessTitle, + message: VectorL10n.keyVerificationVerifyQrCodeScanOtherCodeSuccessMessage, + preferredStyle: .alert) + + let okAction = UIAlertAction(title: Bundle.mxk_localizedString(forKey: "ok"), style: .default, handler: { _ in + completion() + }) + alert.addAction(okAction) + + if let qrCodeReaderViewController = self.qrCodeReaderViewController { + qrCodeReaderViewController.present(alert, animated: animated, completion: nil) + } + } + + private func checkCameraAccessAndPresentQRCodeReader(animated: Bool) { + guard self.cameraAccessManager.isCameraAvailable else { + self.cameraAccessAlertPresenter.presentCameraUnavailableAlert(from: self, animated: animated) + return + } + + self.cameraAccessManager.askAndRequestCameraAccessIfNeeded { (granted) in + if granted { + self.presentQRCodeReader(animated: animated) + } else { + self.cameraAccessAlertPresenter.presentPermissionDeniedAlert(from: self, animated: animated) + } + } + } + + // MARK: - Actions + + @IBAction private func scanButtonAction(_ sender: Any) { + self.checkCameraAccessAndPresentQRCodeReader(animated: true) + } + + @IBAction private func cannotScanAction(_ sender: Any) { + self.viewModel.process(viewAction: .cannotScan) + } + + @IBAction private func closeButtonAction(_ sender: Any) { + self.viewModel.process(viewAction: .cancel) + } + + private func cancelButtonAction() { + self.viewModel.process(viewAction: .cancel) + } +} + + +// MARK: - KeyVerificationVerifyByScanningViewModelViewDelegate +extension KeyVerificationVerifyByScanningViewController: KeyVerificationVerifyByScanningViewModelViewDelegate { + + func keyVerificationVerifyByScanningViewModel(_ viewModel: KeyVerificationVerifyByScanningViewModelType, didUpdateViewState viewSate: KeyVerificationVerifyByScanningViewState) { + self.render(viewState: viewSate) + } +} + +// MARK: - QRCodeReaderViewControllerDelegate +extension KeyVerificationVerifyByScanningViewController: QRCodeReaderViewControllerDelegate { + + func qrCodeReaderViewController(_ viewController: QRCodeReaderViewController, didFound payloadData: Data) { + self.viewModel.process(viewAction: .scannedCode(payloadData: payloadData)) + } + + func qrCodeReaderViewControllerDidCancel(_ viewController: QRCodeReaderViewController) { + self.dismiss(animated: true, completion: nil) + } +} From 45a0f0819dff68c614611c971b3b9f8b354367cd Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 13 Mar 2020 18:49:00 +0100 Subject: [PATCH 206/282] QR code verification: Implement scanning verification view model and coordinator. --- ...ificationVerifyByScanningCoordinator.swift | 82 ++++++ ...ationVerifyByScanningCoordinatorType.swift | 31 +++ ...rificationVerifyByScanningViewAction.swift | 29 ++ ...erificationVerifyByScanningViewModel.swift | 248 ++++++++++++++++++ ...icationVerifyByScanningViewModelType.swift | 38 +++ ...erificationVerifyByScanningViewState.swift | 35 +++ .../Reader/QRCodeReaderViewController.swift | 2 +- 7 files changed, 464 insertions(+), 1 deletion(-) create mode 100644 Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningCoordinator.swift create mode 100644 Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningCoordinatorType.swift create mode 100644 Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewAction.swift create mode 100644 Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift create mode 100644 Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewModelType.swift create mode 100644 Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewState.swift diff --git a/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningCoordinator.swift b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningCoordinator.swift new file mode 100644 index 0000000000..344c1be444 --- /dev/null +++ b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningCoordinator.swift @@ -0,0 +1,82 @@ +// File created from ScreenTemplate +// $ createScreen.sh Verify KeyVerificationVerifyByScanning +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation +import UIKit + +final class KeyVerificationVerifyByScanningCoordinator: KeyVerificationVerifyByScanningCoordinatorType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let keyVerificationRequest: MXKeyVerificationRequest + + private var keyVerificationVerifyByScanningViewModel: KeyVerificationVerifyByScanningViewModelType + private let keyVerificationVerifyByScanningViewController: KeyVerificationVerifyByScanningViewController + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: KeyVerificationVerifyByScanningCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, keyVerificationRequest: MXKeyVerificationRequest) { + self.session = session + self.keyVerificationRequest = keyVerificationRequest + + let keyVerificationVerifyByScanningViewModel = KeyVerificationVerifyByScanningViewModel(session: self.session, keyVerificationRequest: keyVerificationRequest) + let keyVerificationVerifyByScanningViewController = KeyVerificationVerifyByScanningViewController.instantiate(with: keyVerificationVerifyByScanningViewModel) + self.keyVerificationVerifyByScanningViewModel = keyVerificationVerifyByScanningViewModel + self.keyVerificationVerifyByScanningViewController = keyVerificationVerifyByScanningViewController + } + + // MARK: - Public methods + + func start() { + self.keyVerificationVerifyByScanningViewModel.coordinatorDelegate = self + } + + func toPresentable() -> UIViewController { + return self.keyVerificationVerifyByScanningViewController + } +} + +// MARK: - KeyVerificationVerifyByScanningViewModelCoordinatorDelegate +extension KeyVerificationVerifyByScanningCoordinator: KeyVerificationVerifyByScanningViewModelCoordinatorDelegate { + + func keyVerificationVerifyByScanningViewModelDidCancel(_ viewModel: KeyVerificationVerifyByScanningViewModelType) { + self.delegate?.keyVerificationVerifyByScanningCoordinatorDidCancel(self) + } + + func keyVerificationVerifyByScanningViewModelCannotScan(_ viewModel: KeyVerificationVerifyByScanningViewModelType) { + self.delegate?.keyVerificationVerifyByScanningCoordinatorCannotScan(self) + } + + func keyVerificationVerifyByScanningViewModel(_ viewModel: KeyVerificationVerifyByScanningViewModelType, didStartSASVerificationWithTransaction transaction: MXSASTransaction) { + self.delegate?.keyVerificationVerifyByScanningCoordinator(self, didCompleteWithSASTransaction: transaction) + } + + func keyVerificationVerifyByScanningViewModelDidCompleteQRCodeVerification(_ viewModel: KeyVerificationVerifyByScanningViewModelType) { + self.delegate?.keyVerificationVerifyByScanningCoordinatorDidCompleteQRCodeVerification(self) + } +} diff --git a/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningCoordinatorType.swift b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningCoordinatorType.swift new file mode 100644 index 0000000000..50cb3a3807 --- /dev/null +++ b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningCoordinatorType.swift @@ -0,0 +1,31 @@ +// File created from ScreenTemplate +// $ createScreen.sh Verify KeyVerificationVerifyByScanning +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol KeyVerificationVerifyByScanningCoordinatorDelegate: class { + func keyVerificationVerifyByScanningCoordinatorDidCancel(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType) + func keyVerificationVerifyByScanningCoordinatorCannotScan(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType) + func keyVerificationVerifyByScanningCoordinatorDidCompleteQRCodeVerification(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType) + func keyVerificationVerifyByScanningCoordinator(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType, didCompleteWithSASTransaction transaction: MXSASTransaction) +} + +/// `KeyVerificationVerifyByScanningCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow. +protocol KeyVerificationVerifyByScanningCoordinatorType: Coordinator, Presentable { + var delegate: KeyVerificationVerifyByScanningCoordinatorDelegate? { get } +} diff --git a/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewAction.swift b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewAction.swift new file mode 100644 index 0000000000..617dd202c8 --- /dev/null +++ b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewAction.swift @@ -0,0 +1,29 @@ +// File created from ScreenTemplate +// $ createScreen.sh Verify KeyVerificationVerifyByScanning +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// KeyVerificationVerifyByScanningViewController view actions exposed to view model +enum KeyVerificationVerifyByScanningViewAction { + case loadData + case cancel + case scannedCode(payloadData: Data) + case cannotScan + case acknowledgeOtherScannedMyCode(Bool) + case acknowledgeMyUserScannedOtherCode +} diff --git a/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift new file mode 100644 index 0000000000..b4cfc88553 --- /dev/null +++ b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift @@ -0,0 +1,248 @@ +// File created from ScreenTemplate +// $ createScreen.sh Verify KeyVerificationVerifyByScanning +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +enum KeyVerificationVerifyByScanningViewModelError: Error { + case unknown +} + +final class KeyVerificationVerifyByScanningViewModel: KeyVerificationVerifyByScanningViewModelType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let keyVerificationRequest: MXKeyVerificationRequest + private let qrCodeDataCoder: MXQRCodeDataCoder + private let keyVerificationManager: MXKeyVerificationManager + + private var qrCodeTransaction: MXQRCodeTransaction? + private var scannedQRCodeData: MXQRCodeData? + + // MARK: Public + + weak var viewDelegate: KeyVerificationVerifyByScanningViewModelViewDelegate? + weak var coordinatorDelegate: KeyVerificationVerifyByScanningViewModelCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, keyVerificationRequest: MXKeyVerificationRequest) { + self.session = session + self.keyVerificationManager = self.session.crypto.keyVerificationManager + self.keyVerificationRequest = keyVerificationRequest + self.qrCodeDataCoder = MXQRCodeDataCoder() + } + + deinit { + // TODO: Remove QR code transaction if needed + } + + // MARK: - Public + + func process(viewAction: KeyVerificationVerifyByScanningViewAction) { + switch viewAction { + case .loadData: + self.loadData() + case .scannedCode(payloadData: let payloadData): + self.scannedQRCode(payloadData: payloadData) + case .cannotScan: + self.startSASVerification() + case .acknowledgeOtherScannedMyCode(let acknowledgeOtherScannedMyCode): + self.acknowledgeOtherScannedMyCode(acknowledgeOtherScannedMyCode) + case .cancel: + self.coordinatorDelegate?.keyVerificationVerifyByScanningViewModelDidCancel(self) + case .acknowledgeMyUserScannedOtherCode: + self.acknowledgeScanOtherCode() + } + } + + // MARK: - Private + + private func loadData() { + + let qrCodePlayloadData: Data? + let canShowScanAction: Bool + + // TODO: QRcode fetch existing Qr code transaction +// let qrCodeTransaction: MXQRCodeTransaction? = nil + self.qrCodeTransaction = self.keyVerificationManager.qrCodeTransaction(withTransactionId: self.keyVerificationRequest.requestId) + + if let supportedVerificationMethods = self.keyVerificationRequest.myMethods { + + if let qrCodeData = self.qrCodeTransaction?.qrCodeData { + qrCodePlayloadData = self.qrCodeDataCoder.encode(qrCodeData) + } else { + qrCodePlayloadData = nil + } + + canShowScanAction = self.canShowScanAction(from: supportedVerificationMethods) + } else { + qrCodePlayloadData = nil + canShowScanAction = false + } + + let viewData = KeyVerificationVerifyByScanningViewData(qrCodeData: qrCodePlayloadData, + showScanAction: canShowScanAction) + + self.update(viewState: .loaded(viewData: viewData)) + + self.registerTransactionDidStateChangeNotification() + } + + private func canShowScanAction(from verificationMethods: [String]) -> Bool { + return verificationMethods.contains(MXKeyVerificationMethodQRCodeScan) + } + + private func update(viewState: KeyVerificationVerifyByScanningViewState) { + self.viewDelegate?.keyVerificationVerifyByScanningViewModel(self, didUpdateViewState: viewState) + } + + // MARK: QR code + + private func scannedQRCode(payloadData: Data) { + self.scannedQRCodeData = self.qrCodeDataCoder.decode(payloadData) + + let isQRCodeValid = self.scannedQRCodeData != nil + + self.update(viewState: .scannedCodeValidated(isValid: isQRCodeValid)) + } + + private func acknowledgeScanOtherCode() { + guard let scannedQRCodeData = self.scannedQRCodeData else { + return + } + + guard let qrCodeTransaction = self.qrCodeTransaction else { + return + } + + qrCodeTransaction.userHasScannedOtherQrCodeData(scannedQRCodeData) + self.update(viewState: .loading) + } + + private func acknowledgeOtherScannedMyCode(_ acknowledgeOtherScannedMyCode: Bool) { + guard let qrCodeTransaction = self.qrCodeTransaction else { + return + } + self.update(viewState: .loading) + qrCodeTransaction.otherUserScannedMyQrCode(acknowledgeOtherScannedMyCode) + } + + // MARK: SAS + + private func startSASVerification() { + + self.update(viewState: .loading) + + self.session.crypto.keyVerificationManager.beginKeyVerification(from: self.keyVerificationRequest, method: MXKeyVerificationMethodSAS, success: { [weak self] (deviceVerificationTransaction) in + guard let self = self else { + return + } + + // TODO: Remove QR code transaction + + if deviceVerificationTransaction is MXOutgoingSASTransaction == false { + NSLog("[KeyVerificationVerifyByScanningViewModel] SAS transaction should be outgoing") + self.unregisterTransactionDidStateChangeNotification() + self.update(viewState: .error(KeyVerificationVerifyByScanningViewModelError.unknown)) + } + + }, failure: { [weak self] (error) in + guard let self = self else { + return + } + self.update(viewState: .error(error)) + } + ) + } + + // MARK: - MXKeyVerificationTransactionDidChange + + private func registerTransactionDidStateChangeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: .MXKeyVerificationTransactionDidChange, object: nil) + } + + private func unregisterTransactionDidStateChangeNotification() { + NotificationCenter.default.removeObserver(self, name: .MXKeyVerificationTransactionDidChange, object: nil) + } + + @objc private func transactionDidStateChange(notification: Notification) { + guard let transaction = notification.object as? MXKeyVerificationTransaction else { + return + } + + guard let transactionDMEventId = transaction.dmEventId, + self.keyVerificationRequest.requestId == transactionDMEventId else { + return + } + + if let sasTransaction = transaction as? MXSASTransaction { + self.sasTransactionDidStateChange(sasTransaction) + } else if let qrCodeTransaction = transaction as? MXQRCodeTransaction { + self.qrCodeTransactionDidStateChange(qrCodeTransaction) + } + } + + private func sasTransactionDidStateChange(_ transaction: MXSASTransaction) { + switch transaction.state { + case MXSASTransactionStateShowSAS: + self.unregisterTransactionDidStateChangeNotification() + self.coordinatorDelegate?.keyVerificationVerifyByScanningViewModel(self, didStartSASVerificationWithTransaction: transaction) + case MXSASTransactionStateCancelled: + guard let reason = transaction.reasonCancelCode else { + return + } + self.unregisterTransactionDidStateChangeNotification() + self.update(viewState: .cancelled(reason)) + case MXSASTransactionStateCancelledByMe: + guard let reason = transaction.reasonCancelCode else { + return + } + self.unregisterTransactionDidStateChangeNotification() + self.update(viewState: .cancelledByMe(reason)) + default: + break + } + } + + private func qrCodeTransactionDidStateChange(_ transaction: MXQRCodeTransaction) { + switch transaction.state { + case .verified: + self.unregisterTransactionDidStateChangeNotification() + self.coordinatorDelegate?.keyVerificationVerifyByScanningViewModelDidCompleteQRCodeVerification(self) + case .qrScannedByOther: + self.update(viewState: .otherUserScannedMyCode) + case .cancelled: + guard let reason = transaction.reasonCancelCode else { + return + } + self.unregisterTransactionDidStateChangeNotification() + self.update(viewState: .cancelled(reason)) + case .cancelledByMe: + guard let reason = transaction.reasonCancelCode else { + return + } + self.unregisterTransactionDidStateChangeNotification() + self.update(viewState: .cancelledByMe(reason)) + default: + break + } + } +} diff --git a/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewModelType.swift b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewModelType.swift new file mode 100644 index 0000000000..561de52770 --- /dev/null +++ b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewModelType.swift @@ -0,0 +1,38 @@ +// File created from ScreenTemplate +// $ createScreen.sh Verify KeyVerificationVerifyByScanning +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol KeyVerificationVerifyByScanningViewModelViewDelegate: class { + func keyVerificationVerifyByScanningViewModel(_ viewModel: KeyVerificationVerifyByScanningViewModelType, didUpdateViewState viewSate: KeyVerificationVerifyByScanningViewState) +} + +protocol KeyVerificationVerifyByScanningViewModelCoordinatorDelegate: class { + func keyVerificationVerifyByScanningViewModelDidCancel(_ viewModel: KeyVerificationVerifyByScanningViewModelType) + func keyVerificationVerifyByScanningViewModelDidCompleteQRCodeVerification(_ viewModel: KeyVerificationVerifyByScanningViewModelType) + func keyVerificationVerifyByScanningViewModel(_ viewModel: KeyVerificationVerifyByScanningViewModelType, didStartSASVerificationWithTransaction transaction: MXSASTransaction) +} + +/// Protocol describing the view model used by `KeyVerificationVerifyByScanningViewController` +protocol KeyVerificationVerifyByScanningViewModelType { + + var viewDelegate: KeyVerificationVerifyByScanningViewModelViewDelegate? { get set } + var coordinatorDelegate: KeyVerificationVerifyByScanningViewModelCoordinatorDelegate? { get set } + + func process(viewAction: KeyVerificationVerifyByScanningViewAction) +} diff --git a/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewState.swift b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewState.swift new file mode 100644 index 0000000000..df48f6e912 --- /dev/null +++ b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewState.swift @@ -0,0 +1,35 @@ +// File created from ScreenTemplate +// $ createScreen.sh Verify KeyVerificationVerifyByScanning +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +struct KeyVerificationVerifyByScanningViewData { + let qrCodeData: Data? + let showScanAction: Bool +} + +/// KeyVerificationVerifyByScanningViewController view state +enum KeyVerificationVerifyByScanningViewState { + case loading + case loaded(viewData: KeyVerificationVerifyByScanningViewData) + case scannedCodeValidated(isValid: Bool) + case otherUserScannedMyCode + case cancelled(MXTransactionCancelCode) + case cancelledByMe(MXTransactionCancelCode) + case error(Error) +} diff --git a/Riot/Modules/DeviceVerification/Verify/Scanning/QRCode/Reader/QRCodeReaderViewController.swift b/Riot/Modules/DeviceVerification/Verify/Scanning/QRCode/Reader/QRCodeReaderViewController.swift index 47f032ef0f..c3bb92fded 100644 --- a/Riot/Modules/DeviceVerification/Verify/Scanning/QRCode/Reader/QRCodeReaderViewController.swift +++ b/Riot/Modules/DeviceVerification/Verify/Scanning/QRCode/Reader/QRCodeReaderViewController.swift @@ -262,7 +262,7 @@ extension QRCodeReaderViewController: ZXCaptureDelegate { if let bytes = result.resultMetadata.object(forKey: kResultMetadataTypeByteSegments.rawValue) as? NSArray, let byteArray = bytes.firstObject as? ZXByteArray { - let data = Data(bytes: UnsafeRawPointer(byteArray.array), count: Int(byteArray.length)) + let data = Data(bytes: UnsafeRawPointer(byteArray.array), count: Int(byteArray.length)) self.delegate?.qrCodeReaderViewController(self, didFound: data) } From 409c0783dc980722d2d614e479da47a79e5f5d9e Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 13 Mar 2020 18:49:52 +0100 Subject: [PATCH 207/282] QR code verification: Add KeyVerificationService specific to Riot. --- .../KeyVerificationService.swift | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 Riot/Modules/DeviceVerification/KeyVerificationService.swift diff --git a/Riot/Modules/DeviceVerification/KeyVerificationService.swift b/Riot/Modules/DeviceVerification/KeyVerificationService.swift new file mode 100644 index 0000000000..4df2095f36 --- /dev/null +++ b/Riot/Modules/DeviceVerification/KeyVerificationService.swift @@ -0,0 +1,40 @@ +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +final class KeyVerificationService { + + private let cameraAccessManager: CameraAccessManager + + init() { + self.cameraAccessManager = CameraAccessManager() + } + + func supportedKeyVerificationMethods() -> [String] { + var supportedMethods: [String] = [ + MXKeyVerificationMethodSAS, + MXKeyVerificationMethodQRCodeShow, + MXKeyVerificationMethodReciprocate + ] + + if self.cameraAccessManager.isCameraAvailable { + supportedMethods.append(MXKeyVerificationMethodQRCodeScan) + } + + return supportedMethods + } +} From e8fed6aa0875fe74c6245fe11891d62df3d24913 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 13 Mar 2020 18:52:02 +0100 Subject: [PATCH 208/282] Key verification: Update data loading flow for new QR code screen. --- ...ceVerificationDataLoadingCoordinator.swift | 4 ++ ...rificationDataLoadingCoordinatorType.swift | 1 + ...viceVerificationDataLoadingViewModel.swift | 58 +------------------ ...VerificationDataLoadingViewModelType.swift | 1 + 4 files changed, 9 insertions(+), 55 deletions(-) diff --git a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingCoordinator.swift b/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingCoordinator.swift index 461e2fd439..3c1ad6289e 100644 --- a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingCoordinator.swift +++ b/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingCoordinator.swift @@ -65,6 +65,10 @@ final class DeviceVerificationDataLoadingCoordinator: DeviceVerificationDataLoad // MARK: - DeviceVerificationDataLoadingViewModelCoordinatorDelegate extension DeviceVerificationDataLoadingCoordinator: DeviceVerificationDataLoadingViewModelCoordinatorDelegate { + func deviceVerificationDataLoadingViewModel(_ viewModel: DeviceVerificationDataLoadingViewModelType, didAcceptKeyVerificationRequest keyVerificationRequest: MXKeyVerificationRequest) { + self.delegate?.deviceVerificationDataLoadingCoordinator(self, didAcceptKeyVerificationRequest: keyVerificationRequest) + } + func deviceVerificationDataLoadingViewModel(_ viewModel: DeviceVerificationDataLoadingViewModelType, didAcceptKeyVerificationWithTransaction transaction: MXKeyVerificationTransaction) { self.delegate?.deviceVerificationDataLoadingCoordinator(self, didAcceptKeyVerificationRequestWithTransaction: transaction) } diff --git a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingCoordinatorType.swift b/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingCoordinatorType.swift index 057059ab46..7caeea7027 100644 --- a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingCoordinatorType.swift +++ b/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingCoordinatorType.swift @@ -21,6 +21,7 @@ import Foundation protocol DeviceVerificationDataLoadingCoordinatorDelegate: class { func deviceVerificationDataLoadingCoordinator(_ coordinator: DeviceVerificationDataLoadingCoordinatorType, didLoadUser user: MXUser, device: MXDeviceInfo) func deviceVerificationDataLoadingCoordinator(_ coordinator: DeviceVerificationDataLoadingCoordinatorType, didAcceptKeyVerificationRequestWithTransaction transaction: MXKeyVerificationTransaction) + func deviceVerificationDataLoadingCoordinator(_ coordinator: DeviceVerificationDataLoadingCoordinatorType, didAcceptKeyVerificationRequest keyVerificationRequest: MXKeyVerificationRequest) func deviceVerificationDataLoadingCoordinatorDidCancel(_ coordinator: DeviceVerificationDataLoadingCoordinatorType) } diff --git a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewModel.swift b/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewModel.swift index 693164375b..79a8ecf48c 100644 --- a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewModel.swift +++ b/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewModel.swift @@ -33,6 +33,7 @@ final class DeviceVerificationDataLoadingViewModel: DeviceVerificationDataLoadin private let session: MXSession private let otherUserId: String? private let otherDeviceId: String? + private let keyVerificationService = KeyVerificationService() private let keyVerificationRequest: MXKeyVerificationRequest? @@ -88,31 +89,12 @@ final class DeviceVerificationDataLoadingViewModel: DeviceVerificationDataLoadin self.update(viewState: .loading) - // TODO: Advertise that we support QR code too - keyVerificationRequest.accept(withMethods: [MXKeyVerificationMethodSAS], success: { [weak self] in + keyVerificationRequest.accept(withMethods: self.keyVerificationService.supportedKeyVerificationMethods(), success: { [weak self] in guard let self = self else { return } - // TODO: Display QR code and the emoji button here (that depends of keyVerificationRequest.methods) - // Instead of starting the transaction right now - self.session.crypto.keyVerificationManager.beginKeyVerification(from: keyVerificationRequest, method: MXKeyVerificationMethodSAS, success: { [weak self] (deviceVerificationTransaction) in - guard let self = self else { - return - } - - if let outgoingSASTransaction = deviceVerificationTransaction as? MXOutgoingSASTransaction { - self.registerTransactionDidStateChangeNotification(transaction: outgoingSASTransaction) - } else { - self.update(viewState: .error(DeviceVerificationDataLoadingViewModelError.unknown)) - } - - }, failure: { [weak self] (error) in - guard let self = self else { - return - } - self.update(viewState: .error(error)) - }) + self.coordinatorDelegate?.deviceVerificationDataLoadingViewModel(self, didAcceptKeyVerificationRequest: keyVerificationRequest) }, failure: { [weak self] (error) in guard let self = self else { @@ -165,38 +147,4 @@ final class DeviceVerificationDataLoadingViewModel: DeviceVerificationDataLoadin private func update(viewState: DeviceVerificationDataLoadingViewState) { self.viewDelegate?.deviceVerificationDataLoadingViewModel(self, didUpdateViewState: viewState) } - - // MARK: MXKeyVerificationTransactionDidChange - - private func registerTransactionDidStateChangeNotification(transaction: MXOutgoingSASTransaction) { - NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: NSNotification.Name.MXKeyVerificationTransactionDidChange, object: transaction) - } - - private func unregisterTransactionDidStateChangeNotification() { - NotificationCenter.default.removeObserver(self, name: .MXKeyVerificationTransactionDidChange, object: nil) - } - - @objc private func transactionDidStateChange(notification: Notification) { - guard let transaction = notification.object as? MXOutgoingSASTransaction else { - return - } - - switch transaction.state { - case MXSASTransactionStateShowSAS: - self.unregisterTransactionDidStateChangeNotification() - self.update(viewState: .loaded) - self.coordinatorDelegate?.deviceVerificationDataLoadingViewModel(self, didAcceptKeyVerificationWithTransaction: transaction) - case MXSASTransactionStateCancelled: - self.unregisterTransactionDidStateChangeNotification() - self.update(viewState: .error(DeviceVerificationDataLoadingViewModelError.transactionCancelled)) - case MXSASTransactionStateCancelledByMe: - guard let reason = transaction.reasonCancelCode else { - return - } - self.unregisterTransactionDidStateChangeNotification() - self.update(viewState: .error(DeviceVerificationDataLoadingViewModelError.transactionCancelledByMe(reason: reason))) - default: - break - } - } } diff --git a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewModelType.swift b/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewModelType.swift index c553b43175..89ee685234 100644 --- a/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewModelType.swift +++ b/Riot/Modules/DeviceVerification/Loading/DeviceVerificationDataLoadingViewModelType.swift @@ -25,6 +25,7 @@ protocol DeviceVerificationDataLoadingViewModelViewDelegate: class { protocol DeviceVerificationDataLoadingViewModelCoordinatorDelegate: class { func deviceVerificationDataLoadingViewModel(_ viewModel: DeviceVerificationDataLoadingViewModelType, didLoadUser user: MXUser, device: MXDeviceInfo) func deviceVerificationDataLoadingViewModel(_ viewModel: DeviceVerificationDataLoadingViewModelType, didAcceptKeyVerificationWithTransaction transaction: MXKeyVerificationTransaction) + func deviceVerificationDataLoadingViewModel(_ viewModel: DeviceVerificationDataLoadingViewModelType, didAcceptKeyVerificationRequest keyVerificationRequest: MXKeyVerificationRequest) func deviceVerificationDataLoadingViewModelDidCancel(_ viewModel: DeviceVerificationDataLoadingViewModelType) } From da19aff0345efe2fc4a13774ea49479807ebb8e8 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 13 Mar 2020 19:00:14 +0100 Subject: [PATCH 209/282] User verification: Update start verification flow. --- .../UserVerificationStartCoordinator.swift | 5 ++ ...UserVerificationStartCoordinatorType.swift | 3 + .../UserVerificationStartViewModel.swift | 70 +++++-------------- .../UserVerificationStartViewModelType.swift | 2 + 4 files changed, 26 insertions(+), 54 deletions(-) diff --git a/Riot/Modules/UserVerification/Start/UserVerificationStartCoordinator.swift b/Riot/Modules/UserVerification/Start/UserVerificationStartCoordinator.swift index ffc2ce99fc..397a389145 100644 --- a/Riot/Modules/UserVerification/Start/UserVerificationStartCoordinator.swift +++ b/Riot/Modules/UserVerification/Start/UserVerificationStartCoordinator.swift @@ -63,6 +63,11 @@ final class UserVerificationStartCoordinator: UserVerificationStartCoordinatorTy // MARK: - UserVerificationStartViewModelCoordinatorDelegate extension UserVerificationStartCoordinator: UserVerificationStartViewModelCoordinatorDelegate { + + func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, otherDidAcceptRequest request: MXKeyVerificationRequest) { + self.delegate?.userVerificationStartCoordinator(self, otherDidAcceptRequest: request) + } + func userVerificationStartViewModelDidCancel(_ viewModel: UserVerificationStartViewModelType) { self.delegate?.userVerificationStartCoordinatorDidCancel(self) } diff --git a/Riot/Modules/UserVerification/Start/UserVerificationStartCoordinatorType.swift b/Riot/Modules/UserVerification/Start/UserVerificationStartCoordinatorType.swift index 2f59b19732..0d9288bb78 100644 --- a/Riot/Modules/UserVerification/Start/UserVerificationStartCoordinatorType.swift +++ b/Riot/Modules/UserVerification/Start/UserVerificationStartCoordinatorType.swift @@ -19,6 +19,9 @@ import Foundation protocol UserVerificationStartCoordinatorDelegate: class { + + func userVerificationStartCoordinator(_ coordinator: UserVerificationStartCoordinatorType, otherDidAcceptRequest request: MXKeyVerificationRequest) + func userVerificationStartCoordinator(_ coordinator: UserVerificationStartCoordinatorType, didCompleteWithOutgoingTransaction transaction: MXSASTransaction) func userVerificationStartCoordinator(_ coordinator: UserVerificationStartCoordinatorType, didTransactionCancelled transaction: MXSASTransaction) diff --git a/Riot/Modules/UserVerification/Start/UserVerificationStartViewModel.swift b/Riot/Modules/UserVerification/Start/UserVerificationStartViewModel.swift index b893fe752d..9b9c1b6ff5 100644 --- a/Riot/Modules/UserVerification/Start/UserVerificationStartViewModel.swift +++ b/Riot/Modules/UserVerification/Start/UserVerificationStartViewModel.swift @@ -37,6 +37,7 @@ final class UserVerificationStartViewModel: UserVerificationStartViewModelType { private let session: MXSession private let roomMember: MXRoomMember private let verificationManager: MXKeyVerificationManager + private let keyVerificationService: KeyVerificationService private var keyVerificationRequest: MXKeyVerificationRequest? @@ -55,6 +56,7 @@ final class UserVerificationStartViewModel: UserVerificationStartViewModelType { self.session = session self.verificationManager = session.crypto.keyVerificationManager self.roomMember = roomMember + self.keyVerificationService = KeyVerificationService() } deinit { @@ -86,7 +88,7 @@ final class UserVerificationStartViewModel: UserVerificationStartViewModelType { self.verificationManager.requestVerificationByDM(withUserId: self.roomMember.userId, roomId: nil, fallbackText: "", - methods: [MXKeyVerificationMethodSAS], + methods: self.keyVerificationService.supportedKeyVerificationMethods(), success: { [weak self] (keyVerificationRequest) in guard let self = self else { return @@ -94,9 +96,7 @@ final class UserVerificationStartViewModel: UserVerificationStartViewModelType { self.keyVerificationRequest = keyVerificationRequest self.update(viewState: .loaded(self.viewData)) - self.registerKeyVerificationDidChangeNotification(keyVerificationRequest: keyVerificationRequest) - self.registerTransactionDidStateChangeNotification() - + self.registerKeyVerificationRequestDidChangeNotification(for: keyVerificationRequest) }, failure: { [weak self] error in self?.update(viewState: .error(error)) }) @@ -114,55 +114,13 @@ final class UserVerificationStartViewModel: UserVerificationStartViewModelType { keyVerificationRequest.cancel(with: MXTransactionCancelCode.user(), success: nil, failure: nil) } - // MARK: - MXKeyVerificationTransactionDidChange + // MARK: - MXKeyVerificationRequestDidChange - private func registerTransactionDidStateChangeNotification() { - NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: .MXKeyVerificationTransactionDidChange, object: nil) - } - - private func unregisterTransactionDidStateChangeNotification() { - NotificationCenter.default.removeObserver(self, name: .MXKeyVerificationTransactionDidChange, object: nil) - } - - @objc private func transactionDidStateChange(notification: Notification) { - guard let transaction = notification.object as? MXIncomingSASTransaction else { - return - } - - guard let keyVerificationRequest = self.keyVerificationRequest, - let transactionDMEventId = transaction.dmEventId, - keyVerificationRequest.requestId == transactionDMEventId else { - return - } - - switch transaction.state { - case MXSASTransactionStateShowSAS: - self.unregisterTransactionDidStateChangeNotification() - self.coordinatorDelegate?.userVerificationStartViewModel(self, didCompleteWithIncomingTransaction: transaction) - case MXSASTransactionStateCancelled: - guard let reason = transaction.reasonCancelCode else { - return - } - self.unregisterTransactionDidStateChangeNotification() - self.update(viewState: .cancelled(reason)) - case MXSASTransactionStateCancelledByMe: - guard let reason = transaction.reasonCancelCode else { - return - } - self.unregisterTransactionDidStateChangeNotification() - self.update(viewState: .cancelledByMe(reason)) - default: - break - } - } - - // MARK: - MXKeyVerificationTransactionDidChange - - private func registerKeyVerificationDidChangeNotification(keyVerificationRequest: MXKeyVerificationRequest) { + private func registerKeyVerificationRequestDidChangeNotification(for keyVerificationRequest: MXKeyVerificationRequest) { NotificationCenter.default.addObserver(self, selector: #selector(keyVerificationRequestDidChange(notification:)), name: .MXKeyVerificationRequestDidChange, object: keyVerificationRequest) } - private func unregisterKeyVerificationDidChangeNotification() { + private func unregisterKeyVerificationRequestDidChangeNotification() { NotificationCenter.default.removeObserver(self, name: .MXKeyVerificationRequestDidChange, object: nil) } @@ -176,22 +134,26 @@ final class UserVerificationStartViewModel: UserVerificationStartViewModelType { } switch keyVerificationRequest.state { - case MXKeyVerificationRequestStateAccepted: - self.unregisterKeyVerificationDidChangeNotification() + case MXKeyVerificationRequestStateAccepted: + self.unregisterKeyVerificationRequestDidChangeNotification() + self.coordinatorDelegate?.userVerificationStartViewModel(self, otherDidAcceptRequest: currentKeyVerificationRequest) + case MXKeyVerificationRequestStateReady: + self.unregisterKeyVerificationRequestDidChangeNotification() + self.coordinatorDelegate?.userVerificationStartViewModel(self, otherDidAcceptRequest: currentKeyVerificationRequest) case MXKeyVerificationRequestStateCancelled: guard let reason = keyVerificationRequest.reasonCancelCode else { return } - self.unregisterKeyVerificationDidChangeNotification() + self.unregisterKeyVerificationRequestDidChangeNotification() self.update(viewState: .cancelled(reason)) case MXKeyVerificationRequestStateCancelledByMe: guard let reason = keyVerificationRequest.reasonCancelCode else { return } - self.unregisterKeyVerificationDidChangeNotification() + self.unregisterKeyVerificationRequestDidChangeNotification() self.update(viewState: .cancelledByMe(reason)) case MXKeyVerificationRequestStateExpired: - self.unregisterKeyVerificationDidChangeNotification() + self.unregisterKeyVerificationRequestDidChangeNotification() self.update(viewState: .error(UserVerificationStartViewModelError.keyVerificationRequestExpired)) default: break diff --git a/Riot/Modules/UserVerification/Start/UserVerificationStartViewModelType.swift b/Riot/Modules/UserVerification/Start/UserVerificationStartViewModelType.swift index ce8feb50a2..4768288d15 100644 --- a/Riot/Modules/UserVerification/Start/UserVerificationStartViewModelType.swift +++ b/Riot/Modules/UserVerification/Start/UserVerificationStartViewModelType.swift @@ -24,6 +24,8 @@ protocol UserVerificationStartViewModelViewDelegate: class { protocol UserVerificationStartViewModelCoordinatorDelegate: class { + func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, otherDidAcceptRequest request: MXKeyVerificationRequest) + func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, didCompleteWithIncomingTransaction transaction: MXSASTransaction) func userVerificationStartViewModel(_ viewModel: UserVerificationStartViewModelType, didTransactionCancelled transaction: MXSASTransaction) From e70e2f92b787c7886d60253c75d7e68128db0c80 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 13 Mar 2020 19:00:28 +0100 Subject: [PATCH 210/282] QR code verification: Update scanning verification screen. --- .../KeyVerificationVerifyByScanningViewController.storyboard | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewController.storyboard b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewController.storyboard index 73ef5980a2..7aa50203ca 100644 --- a/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewController.storyboard +++ b/Riot/Modules/DeviceVerification/Verify/Scanning/KeyVerificationVerifyByScanningViewController.storyboard @@ -67,7 +67,7 @@ - - + - + + - @@ -98,9 +95,10 @@ - + + From 7e0331a9cbf98df165ef3b3eb3b7b2738e6564c6 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 8 Apr 2020 12:36:34 +0200 Subject: [PATCH 251/282] RoomParticipantsViewController: Handle power level label instead of badge. --- .../Members/RoomParticipantsViewController.m | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/Riot/Modules/Room/Members/RoomParticipantsViewController.m b/Riot/Modules/Room/Members/RoomParticipantsViewController.m index 6944e0900b..270bd94cb3 100644 --- a/Riot/Modules/Room/Members/RoomParticipantsViewController.m +++ b/Riot/Modules/Room/Members/RoomParticipantsViewController.m @@ -1127,20 +1127,28 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N { MXRoomState *roomState = self.mxRoom.dangerousSyncState; - // Update member badge + // Update member power level MXRoomPowerLevels *powerLevels = [roomState powerLevels]; NSInteger powerLevel = [powerLevels powerLevelOfUserWithUserID:contact.mxMember.userId]; - if (powerLevel >= RoomPowerLevelAdmin) - { - participantCell.thumbnailBadgeView.image = [UIImage imageNamed:@"admin_icon"]; - participantCell.thumbnailBadgeView.hidden = NO; - } - else if (powerLevel >= RoomPowerLevelModerator) - { - participantCell.thumbnailBadgeView.image = [UIImage imageNamed:@"mod_icon"]; - participantCell.thumbnailBadgeView.hidden = NO; + + RoomPowerLevel roomPowerLevel = [RoomPowerLevelHelper roomPowerLevelFrom:powerLevel]; + + NSString *powerLevelText; + + switch (roomPowerLevel) { + case RoomPowerLevelAdmin: + powerLevelText = NSLocalizedStringFromTable(@"room_member_power_level_short_admin", @"Vector", nil); + break; + case RoomPowerLevelModerator: + powerLevelText = NSLocalizedStringFromTable(@"room_member_power_level_short_moderator", @"Vector", nil); + break; + default: + powerLevelText = nil; + break; } + participantCell.powerLevelLabel.text = powerLevelText; + // Update the contact display name by considering the current room state. if (contact.mxMember.userId) { From e54c9d110368b950e15660c8c78fbf74df4b2456 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 8 Apr 2020 12:36:48 +0200 Subject: [PATCH 252/282] GroupParticipantsViewController: Handle power level label instead of badge. --- .../Members/GroupParticipantsViewController.m | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Riot/Modules/Communities/Members/GroupParticipantsViewController.m b/Riot/Modules/Communities/Members/GroupParticipantsViewController.m index fb9e86b8b6..b4c48231b9 100644 --- a/Riot/Modules/Communities/Members/GroupParticipantsViewController.m +++ b/Riot/Modules/Communities/Members/GroupParticipantsViewController.m @@ -826,12 +826,15 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N { [participantCell render:contact]; - // Update member badge + NSString *powerLevelText; + + // Update power level label if (contact.mxGroupUser.isPrivileged) { - participantCell.thumbnailBadgeView.image = [UIImage imageNamed:@"admin_icon"]; - participantCell.thumbnailBadgeView.hidden = NO; + powerLevelText = NSLocalizedStringFromTable(@"room_member_power_level_short_admin", @"Vector", nil); } + + participantCell.powerLevelLabel.text = powerLevelText; } cell = participantCell; From 0afdd7af5f7d91c1c57817704979d32d448b8690 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 8 Apr 2020 12:37:52 +0200 Subject: [PATCH 253/282] RoomMemberDetailsViewController: Add power level label instead of badge. --- .../Detail/RoomMemberDetailsViewController.m | 40 ++++++++++---- .../RoomMemberDetailsViewController.xib | 55 +++++++++++++++---- 2 files changed, 72 insertions(+), 23 deletions(-) diff --git a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m index 9057c3ad41..0b214b04e8 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m @@ -97,6 +97,8 @@ List of the direct chats (room ids) with this member. @property (weak, nonatomic) IBOutlet UIImageView *bottomImageView; +@property (weak, nonatomic) IBOutlet UILabel *roomMemberPowerLevelLabel; +@property (weak, nonatomic) IBOutlet UIView *roomMemberPowerLevelContainerView; @property(nonatomic) UserEncryptionTrustLevel encryptionTrustLevel; @@ -256,6 +258,7 @@ - (void)userInterfaceThemeDidChange self.roomMemberNameLabel.textColor = ThemeService.shared.theme.baseTextPrimaryColor; self.roomMemberStatusLabel.textColor = ThemeService.shared.theme.tintColor; + self.roomMemberPowerLevelLabel.textColor = ThemeService.shared.theme.baseTextPrimaryColor; // Check the table view style to select its bg color. self.tableView.backgroundColor = ((self.tableView.style == UITableViewStylePlain) ? ThemeService.shared.theme.backgroundColor : ThemeService.shared.theme.headerBackgroundColor); @@ -389,27 +392,40 @@ - (void)updateMemberInfo { self.roomMemberNameLabel.text = self.mxRoomMember.displayname ? self.mxRoomMember.displayname : self.mxRoomMember.userId; - // Update member badge + // Update member power level MXWeakify(self); [self.mxRoom state:^(MXRoomState *roomState) { MXStrongifyAndReturnIfNil(self); + NSString *powerLevelTextFormat; + MXRoomPowerLevels *powerLevels = [roomState powerLevels]; NSInteger powerLevel = [powerLevels powerLevelOfUserWithUserID:self.mxRoomMember.userId]; - if (powerLevel >= RoomPowerLevelAdmin) - { - self->memberTitleView.memberBadge.image = [UIImage imageNamed:@"admin_icon"]; - self->memberTitleView.memberBadge.hidden = NO; - } - else if (powerLevel >= RoomPowerLevelModerator) - { - self->memberTitleView.memberBadge.image = [UIImage imageNamed:@"mod_icon"]; - self->memberTitleView.memberBadge.hidden = NO; + + RoomPowerLevel roomPowerLevel = [RoomPowerLevelHelper roomPowerLevelFrom:powerLevel]; + + switch (roomPowerLevel) { + case RoomPowerLevelAdmin: + powerLevelTextFormat = NSLocalizedStringFromTable(@"room_member_power_level_admin_in", @"Vector", nil); + break; + case RoomPowerLevelModerator: + powerLevelTextFormat = NSLocalizedStringFromTable(@"room_member_power_level_moderator_in", @"Vector", nil); + break; + default: + powerLevelTextFormat = nil; + break; } - else + + NSString *powerLevelText; + + if (powerLevelTextFormat) { - self->memberTitleView.memberBadge.hidden = YES; + NSString *roomName = self.mxRoom.summary.displayname; + powerLevelText = [NSString stringWithFormat:powerLevelTextFormat, roomName]; } + + self.roomMemberPowerLevelLabel.text = powerLevelText; + self.roomMemberPowerLevelContainerView.hidden = !powerLevelTextFormat; }]; NSString* presenceText; diff --git a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.xib b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.xib index b723cb4f45..999ddf2fcf 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.xib +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.xib @@ -20,6 +20,8 @@ + + @@ -31,7 +33,7 @@ - + @@ -57,7 +59,7 @@ - + @@ -67,7 +69,6 @@ - @@ -82,6 +83,14 @@ + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + - + - + @@ -107,16 +136,19 @@ + + + - + @@ -139,7 +171,7 @@ - + @@ -167,6 +199,7 @@ + From 06b0f8db9e6ecc5ac4da1573f3e75a8429eb070c Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 8 Apr 2020 12:38:55 +0200 Subject: [PATCH 254/282] RoomMemberTitleView: Remove power level badge. --- .../Members/Detail/Views/RoomMemberTitleView.h | 1 - .../Detail/Views/RoomMemberTitleView.xib | 17 +++-------------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/Riot/Modules/Room/Members/Detail/Views/RoomMemberTitleView.h b/Riot/Modules/Room/Members/Detail/Views/RoomMemberTitleView.h index e81d3faf74..009b77e229 100644 --- a/Riot/Modules/Room/Members/Detail/Views/RoomMemberTitleView.h +++ b/Riot/Modules/Room/Members/Detail/Views/RoomMemberTitleView.h @@ -49,7 +49,6 @@ + (instancetype)roomMemberTitleView; @property (weak, nonatomic) IBOutlet UIView *memberAvatarMask; -@property (weak, nonatomic) IBOutlet UIImageView *memberBadge; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *memberAvatarMaskCenterXConstraint; /** diff --git a/Riot/Modules/Room/Members/Detail/Views/RoomMemberTitleView.xib b/Riot/Modules/Room/Members/Detail/Views/RoomMemberTitleView.xib index b86be3ba75..50eaf408db 100644 --- a/Riot/Modules/Room/Members/Detail/Views/RoomMemberTitleView.xib +++ b/Riot/Modules/Room/Members/Detail/Views/RoomMemberTitleView.xib @@ -1,11 +1,11 @@ - + - + @@ -15,16 +15,8 @@ - - + @@ -35,17 +27,14 @@ - - - From b8f82a6bef302c1e2189c19117c7325f777c3bc4 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 8 Apr 2020 12:42:12 +0200 Subject: [PATCH 255/282] ExpandedRoomTitleView: Reduce trust level badge size. --- .../Room/Views/Title/Expanded/ExpandedRoomTitleView.xib | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Riot/Modules/Room/Views/Title/Expanded/ExpandedRoomTitleView.xib b/Riot/Modules/Room/Views/Title/Expanded/ExpandedRoomTitleView.xib index 537c5168f9..453addbd9e 100644 --- a/Riot/Modules/Room/Views/Title/Expanded/ExpandedRoomTitleView.xib +++ b/Riot/Modules/Room/Views/Title/Expanded/ExpandedRoomTitleView.xib @@ -28,19 +28,19 @@ - + + - - + - + From b81d2b31aea778440e3b0a8ecc9b749f8b2ce049 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 8 Apr 2020 12:51:40 +0200 Subject: [PATCH 256/282] RoomCollectionViewCell: Reduce trust level badge size. --- Riot/Modules/Home/Views/RoomCollectionViewCell.xib | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Riot/Modules/Home/Views/RoomCollectionViewCell.xib b/Riot/Modules/Home/Views/RoomCollectionViewCell.xib index ea0162bc9e..45366f0ff9 100644 --- a/Riot/Modules/Home/Views/RoomCollectionViewCell.xib +++ b/Riot/Modules/Home/Views/RoomCollectionViewCell.xib @@ -53,11 +53,11 @@ - From 8922b2b4267536ac8d530975bd9fa647fb5e0e0d Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 8 Apr 2020 14:05:54 +0200 Subject: [PATCH 257/282] Templates: 2019 -> 2020 --- .../FlowCoordinatorTemplate/FlowTemplateCoordinator.swift | 2 +- .../FlowTemplateCoordinatorBridgePresenter.swift | 2 +- .../FlowCoordinatorTemplate/FlowTemplateCoordinatorType.swift | 2 +- .../buildable/ScreenTemplate/TemplateScreenCoordinator.swift | 2 +- .../ScreenTemplate/TemplateScreenCoordinatorType.swift | 2 +- .../buildable/ScreenTemplate/TemplateScreenViewAction.swift | 2 +- .../buildable/ScreenTemplate/TemplateScreenViewController.swift | 2 +- .../buildable/ScreenTemplate/TemplateScreenViewModel.swift | 2 +- .../buildable/ScreenTemplate/TemplateScreenViewModelType.swift | 2 +- .../buildable/ScreenTemplate/TemplateScreenViewState.swift | 2 +- .../SimpleScreenTemplateViewController.swift | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Tools/Templates/buildable/FlowCoordinatorTemplate/FlowTemplateCoordinator.swift b/Tools/Templates/buildable/FlowCoordinatorTemplate/FlowTemplateCoordinator.swift index 720ed34f49..113b64f942 100644 --- a/Tools/Templates/buildable/FlowCoordinatorTemplate/FlowTemplateCoordinator.swift +++ b/Tools/Templates/buildable/FlowCoordinatorTemplate/FlowTemplateCoordinator.swift @@ -1,5 +1,5 @@ /* - Copyright 2019 New Vector Ltd + Copyright 2020 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. diff --git a/Tools/Templates/buildable/FlowCoordinatorTemplate/FlowTemplateCoordinatorBridgePresenter.swift b/Tools/Templates/buildable/FlowCoordinatorTemplate/FlowTemplateCoordinatorBridgePresenter.swift index 3aeb437640..651e5acd9c 100644 --- a/Tools/Templates/buildable/FlowCoordinatorTemplate/FlowTemplateCoordinatorBridgePresenter.swift +++ b/Tools/Templates/buildable/FlowCoordinatorTemplate/FlowTemplateCoordinatorBridgePresenter.swift @@ -1,5 +1,5 @@ /* - Copyright 2019 New Vector Ltd + Copyright 2020 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. diff --git a/Tools/Templates/buildable/FlowCoordinatorTemplate/FlowTemplateCoordinatorType.swift b/Tools/Templates/buildable/FlowCoordinatorTemplate/FlowTemplateCoordinatorType.swift index cd754d8d4d..4fa5cc5dc6 100644 --- a/Tools/Templates/buildable/FlowCoordinatorTemplate/FlowTemplateCoordinatorType.swift +++ b/Tools/Templates/buildable/FlowCoordinatorTemplate/FlowTemplateCoordinatorType.swift @@ -1,5 +1,5 @@ /* - Copyright 2019 New Vector Ltd + Copyright 2020 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. diff --git a/Tools/Templates/buildable/ScreenTemplate/TemplateScreenCoordinator.swift b/Tools/Templates/buildable/ScreenTemplate/TemplateScreenCoordinator.swift index 8cd322e45c..61fb2f39f0 100644 --- a/Tools/Templates/buildable/ScreenTemplate/TemplateScreenCoordinator.swift +++ b/Tools/Templates/buildable/ScreenTemplate/TemplateScreenCoordinator.swift @@ -1,5 +1,5 @@ /* - Copyright 2019 New Vector Ltd + Copyright 2020 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. diff --git a/Tools/Templates/buildable/ScreenTemplate/TemplateScreenCoordinatorType.swift b/Tools/Templates/buildable/ScreenTemplate/TemplateScreenCoordinatorType.swift index b8869aafd4..2a00f40aec 100644 --- a/Tools/Templates/buildable/ScreenTemplate/TemplateScreenCoordinatorType.swift +++ b/Tools/Templates/buildable/ScreenTemplate/TemplateScreenCoordinatorType.swift @@ -1,5 +1,5 @@ /* - Copyright 2019 New Vector Ltd + Copyright 2020 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. diff --git a/Tools/Templates/buildable/ScreenTemplate/TemplateScreenViewAction.swift b/Tools/Templates/buildable/ScreenTemplate/TemplateScreenViewAction.swift index 8dca49e5cf..11203ab9d0 100644 --- a/Tools/Templates/buildable/ScreenTemplate/TemplateScreenViewAction.swift +++ b/Tools/Templates/buildable/ScreenTemplate/TemplateScreenViewAction.swift @@ -1,5 +1,5 @@ /* - Copyright 2019 New Vector Ltd + Copyright 2020 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. diff --git a/Tools/Templates/buildable/ScreenTemplate/TemplateScreenViewController.swift b/Tools/Templates/buildable/ScreenTemplate/TemplateScreenViewController.swift index cc23f78f3b..7a74a9fec9 100644 --- a/Tools/Templates/buildable/ScreenTemplate/TemplateScreenViewController.swift +++ b/Tools/Templates/buildable/ScreenTemplate/TemplateScreenViewController.swift @@ -1,5 +1,5 @@ /* - Copyright 2019 New Vector Ltd + Copyright 2020 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. diff --git a/Tools/Templates/buildable/ScreenTemplate/TemplateScreenViewModel.swift b/Tools/Templates/buildable/ScreenTemplate/TemplateScreenViewModel.swift index 9b716cddf7..77781f0f5b 100644 --- a/Tools/Templates/buildable/ScreenTemplate/TemplateScreenViewModel.swift +++ b/Tools/Templates/buildable/ScreenTemplate/TemplateScreenViewModel.swift @@ -1,5 +1,5 @@ /* - Copyright 2019 New Vector Ltd + Copyright 2020 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. diff --git a/Tools/Templates/buildable/ScreenTemplate/TemplateScreenViewModelType.swift b/Tools/Templates/buildable/ScreenTemplate/TemplateScreenViewModelType.swift index fd76d45b19..b1ce5cfc2a 100644 --- a/Tools/Templates/buildable/ScreenTemplate/TemplateScreenViewModelType.swift +++ b/Tools/Templates/buildable/ScreenTemplate/TemplateScreenViewModelType.swift @@ -1,5 +1,5 @@ /* - Copyright 2019 New Vector Ltd + Copyright 2020 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. diff --git a/Tools/Templates/buildable/ScreenTemplate/TemplateScreenViewState.swift b/Tools/Templates/buildable/ScreenTemplate/TemplateScreenViewState.swift index 815fd8196d..f4b31b8391 100644 --- a/Tools/Templates/buildable/ScreenTemplate/TemplateScreenViewState.swift +++ b/Tools/Templates/buildable/ScreenTemplate/TemplateScreenViewState.swift @@ -1,5 +1,5 @@ /* - Copyright 2019 New Vector Ltd + Copyright 2020 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. diff --git a/Tools/Templates/buildable/SimpleScreenTemplate/SimpleScreenTemplateViewController.swift b/Tools/Templates/buildable/SimpleScreenTemplate/SimpleScreenTemplateViewController.swift index c9d6a01fb1..2955714c9c 100644 --- a/Tools/Templates/buildable/SimpleScreenTemplate/SimpleScreenTemplateViewController.swift +++ b/Tools/Templates/buildable/SimpleScreenTemplate/SimpleScreenTemplateViewController.swift @@ -1,5 +1,5 @@ /* - Copyright 2019 New Vector Ltd + Copyright 2020 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. From 106f5fdb9cc04aa50683790dc89fbd7be85ccf78 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 8 Apr 2020 15:05:11 +0200 Subject: [PATCH 258/282] Add an helper class for encryption trust level badge generation. --- Riot.xcodeproj/project.pbxproj | 4 ++ ...EncryptionTrustLevelBadgeImageHelper.swift | 59 +++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 Riot/Utils/EncryptionTrustLevelBadgeImageHelper.swift diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index a6abf95871..cf3abe209d 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -277,6 +277,7 @@ B1963B30228F1C4900CBA17F /* BubbleReactionsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1963B2A228F1C4800CBA17F /* BubbleReactionsView.xib */; }; B1963B32228F1C6B00CBA17F /* BubbleReactionsViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1963B31228F1C6B00CBA17F /* BubbleReactionsViewModelType.swift */; }; B1963B3822933BC800CBA17F /* AutosizedCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1963B3722933BC800CBA17F /* AutosizedCollectionView.swift */; }; + B197B7C6243DE947005ABBF3 /* EncryptionTrustLevelBadgeImageHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B197B7C5243DE947005ABBF3 /* EncryptionTrustLevelBadgeImageHelper.swift */; }; B19EFA3921F8BB2C00FC070E /* KeyBackupRecoverCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B19EFA3821F8BB2C00FC070E /* KeyBackupRecoverCoordinatorType.swift */; }; B19EFA3B21F8BB4100FC070E /* KeyBackupRecoverCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B19EFA3A21F8BB4100FC070E /* KeyBackupRecoverCoordinator.swift */; }; B1A5B33E227ADF2A004CBA85 /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1A5B33D227ADF2A004CBA85 /* UIImage.swift */; }; @@ -1088,6 +1089,7 @@ B1963B2A228F1C4800CBA17F /* BubbleReactionsView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BubbleReactionsView.xib; sourceTree = ""; }; B1963B31228F1C6B00CBA17F /* BubbleReactionsViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BubbleReactionsViewModelType.swift; sourceTree = ""; }; B1963B3722933BC800CBA17F /* AutosizedCollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutosizedCollectionView.swift; sourceTree = ""; }; + B197B7C5243DE947005ABBF3 /* EncryptionTrustLevelBadgeImageHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionTrustLevelBadgeImageHelper.swift; sourceTree = ""; }; B19EFA3821F8BB2C00FC070E /* KeyBackupRecoverCoordinatorType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverCoordinatorType.swift; sourceTree = ""; }; B19EFA3A21F8BB4100FC070E /* KeyBackupRecoverCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverCoordinator.swift; sourceTree = ""; }; B1A5B33D227ADF2A004CBA85 /* UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = ""; }; @@ -4219,6 +4221,7 @@ F083BC141E7009EC00A9B29C /* Tools.h */, F083BC151E7009EC00A9B29C /* Tools.m */, B1DB4F0D22316FFF0065DBFA /* UserNameColorGenerator.swift */, + B197B7C5243DE947005ABBF3 /* EncryptionTrustLevelBadgeImageHelper.swift */, ); path = Utils; sourceTree = ""; @@ -5061,6 +5064,7 @@ 32863A5C2384074C00D07C4A /* RiotSettingAllowedWidgets.swift in Sources */, B1B9DEDA22E9B7350065E677 /* SerializationService.swift in Sources */, B1B5572520EE6C4D00210D55 /* RoomMessagesSearchViewController.m in Sources */, + B197B7C6243DE947005ABBF3 /* EncryptionTrustLevelBadgeImageHelper.swift in Sources */, B12D79FD23E2462200FACEDC /* UserVerificationStartViewModelType.swift in Sources */, B1C543AE23A286A000DCA1FA /* KeyVerificationRequestStatusBubbleCell.swift in Sources */, B139C22121FE5D9D00BB68EC /* KeyBackupRecoverFromPassphraseViewState.swift in Sources */, diff --git a/Riot/Utils/EncryptionTrustLevelBadgeImageHelper.swift b/Riot/Utils/EncryptionTrustLevelBadgeImageHelper.swift new file mode 100644 index 0000000000..674989ffc6 --- /dev/null +++ b/Riot/Utils/EncryptionTrustLevelBadgeImageHelper.swift @@ -0,0 +1,59 @@ +/* + Copyright 2020 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 + +@objcMembers +final class EncryptionTrustLevelBadgeImageHelper: NSObject { + + static func roomBadgeImage(for trustLevel: RoomEncryptionTrustLevel) -> UIImage { + + let badgeImage: UIImage + + switch trustLevel { + case .warning: + badgeImage = Asset.Images.encryptionWarning.image + case .normal: + badgeImage = Asset.Images.encryptionNormal.image + case .trusted: + badgeImage = Asset.Images.encryptionTrusted.image + case .unknown: + badgeImage = Asset.Images.encryptionNormal.image + @unknown default: + badgeImage = Asset.Images.encryptionNormal.image + } + + return badgeImage + } + + static func userBadgeImage(for trustLevel: UserEncryptionTrustLevel) -> UIImage? { + + let badgeImage: UIImage? + + switch trustLevel { + case .warning: + badgeImage = Asset.Images.encryptionWarning.image + case .normal: + badgeImage = Asset.Images.encryptionNormal.image + case .trusted: + badgeImage = Asset.Images.encryptionTrusted.image + default: + badgeImage = nil + } + + return badgeImage + } +} From 26ac56d9082fe769d917b6542e8e6cbb3852c196 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 8 Apr 2020 15:06:27 +0200 Subject: [PATCH 259/282] Use EncryptionTrustLevelBadgeImageHelper where needed. --- .../Contacts/Views/ContactTableViewCell.m | 29 +--------------- .../Home/Views/RoomCollectionViewCell.m | 30 +---------------- .../Detail/RoomMemberDetailsViewController.m | 33 ++----------------- 3 files changed, 4 insertions(+), 88 deletions(-) diff --git a/Riot/Modules/Contacts/Views/ContactTableViewCell.m b/Riot/Modules/Contacts/Views/ContactTableViewCell.m index 1a634e8b6b..500d75a798 100644 --- a/Riot/Modules/Contacts/Views/ContactTableViewCell.m +++ b/Riot/Modules/Contacts/Views/ContactTableViewCell.m @@ -247,34 +247,7 @@ - (void)refreshContactBadgeImage userEncryptionTrustLevel = [self.mxRoom encryptionTrustLevelForUserId:matrixId]; } - self.avatarBadgeImageView.image = [self badgeImageForUserEncryptionTrustLevel:userEncryptionTrustLevel]; -} - -- (UIImage*)badgeImageForUserEncryptionTrustLevel:(UserEncryptionTrustLevel)userEncryptionTrustLevel -{ - NSString *encryptionIconName; - UIImage *encryptionIcon; - - switch (userEncryptionTrustLevel) { - case UserEncryptionTrustLevelWarning: - encryptionIconName = @"encryption_warning"; - break; - case UserEncryptionTrustLevelNormal: - encryptionIconName = @"encryption_normal"; - break; - case UserEncryptionTrustLevelTrusted: - encryptionIconName = @"encryption_trusted"; - break; - default: - break; - } - - if (encryptionIconName) - { - encryptionIcon = [UIImage imageNamed:encryptionIconName]; - } - - return encryptionIcon; + self.avatarBadgeImageView.image = [EncryptionTrustLevelBadgeImageHelper userBadgeImageFor:userEncryptionTrustLevel]; } - (void)refreshContactDisplayName diff --git a/Riot/Modules/Home/Views/RoomCollectionViewCell.m b/Riot/Modules/Home/Views/RoomCollectionViewCell.m index 8bd307ecf3..34ccbb861c 100644 --- a/Riot/Modules/Home/Views/RoomCollectionViewCell.m +++ b/Riot/Modules/Home/Views/RoomCollectionViewCell.m @@ -162,7 +162,7 @@ - (void)render:(MXKCellData *)cellData if (roomCellData.roomSummary.isEncrypted) { self.encryptedRoomIcon.hidden = NO; - self.encryptedRoomIcon.image = [self shieldImageForTrustLevel:roomCellData.roomSummary.roomEncryptionTrustLevel]; + self.encryptedRoomIcon.image = [EncryptionTrustLevelBadgeImageHelper roomBadgeImageFor:roomCellData.roomSummary.roomEncryptionTrustLevel]; } else { @@ -215,33 +215,5 @@ - (NSString*)roomId return nil; } -- (UIImage*)shieldImageForTrustLevel:(RoomEncryptionTrustLevel)roomEncryptionTrustLevel -{ - UIImage *shieldImage; - - NSString *encryptionIconName; - switch (roomEncryptionTrustLevel) - { - case RoomEncryptionTrustLevelWarning: - encryptionIconName = @"encryption_warning"; - break; - case RoomEncryptionTrustLevelNormal: - encryptionIconName = @"encryption_normal"; - break; - case RoomEncryptionTrustLevelTrusted: - encryptionIconName = @"encryption_trusted"; - break; - case RoomEncryptionTrustLevelUnknown: - encryptionIconName = @"encryption_normal"; - break; - } - - if (encryptionIconName) - { - shieldImage = [UIImage imageNamed:encryptionIconName]; - } - return shieldImage; -} - @end diff --git a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m index 0b214b04e8..d323154fb1 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m @@ -440,7 +440,7 @@ - (void)updateMemberInfo self.roomMemberStatusLabel.text = presenceText; - self.roomMemberAvatarBadgeImageView.image = self.userEncryptionBadgeImage; + self.roomMemberAvatarBadgeImageView.image = [EncryptionTrustLevelBadgeImageHelper userBadgeImageFor:self.encryptionTrustLevel]; // Retrieve the existing direct chats [directChatsArray removeAllObjects]; @@ -503,35 +503,6 @@ - (void)refreshUserEncryptionTrustLevel [self updateMemberInfo]; } -- (UIImage*)userEncryptionBadgeImage -{ - NSString *encryptionIconName; - UIImage *encryptionIcon; - - UserEncryptionTrustLevel userEncryptionTrustLevel = self.encryptionTrustLevel; - - switch (userEncryptionTrustLevel) { - case UserEncryptionTrustLevelWarning: - encryptionIconName = @"encryption_warning"; - break; - case UserEncryptionTrustLevelNormal: - encryptionIconName = @"encryption_normal"; - break; - case UserEncryptionTrustLevelTrusted: - encryptionIconName = @"encryption_trusted"; - break; - default: - break; - } - - if (encryptionIconName) - { - encryptionIcon = [UIImage imageNamed:encryptionIconName]; - } - - return encryptionIcon; -} - - (BOOL)isRoomMemberCurrentUser { return [self.mxRoomMember.userId isEqualToString:self.mainSession.myUser.userId]; @@ -914,7 +885,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N break; } - securityStatusCell.imageView.image = self.userEncryptionBadgeImage; + securityStatusCell.imageView.image = [EncryptionTrustLevelBadgeImageHelper userBadgeImageFor:self.encryptionTrustLevel]; securityStatusCell.textLabel.numberOfLines = 1; securityStatusCell.textLabel.font = [UIFont systemFontOfSize:16.0]; From ffa560dc81fde7cf34cb24bebff7571e57331ecc Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 8 Apr 2020 15:06:59 +0200 Subject: [PATCH 260/282] RoomTitleView: Add badge image view. --- Riot/Modules/Room/Views/Title/RoomTitleView.h | 1 + Riot/Modules/Room/Views/Title/RoomTitleView.m | 2 ++ .../Room/Views/Title/RoomTitleView.xib | 30 ++++++++++++++----- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/Riot/Modules/Room/Views/Title/RoomTitleView.h b/Riot/Modules/Room/Views/Title/RoomTitleView.h index 23b198f5ec..52fe01acfc 100644 --- a/Riot/Modules/Room/Views/Title/RoomTitleView.h +++ b/Riot/Modules/Room/Views/Title/RoomTitleView.h @@ -39,6 +39,7 @@ @property (weak, nonatomic) IBOutlet UIView *addParticipantMask; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *displayNameCenterXConstraint; @property (weak, nonatomic) IBOutlet UIImageView *roomDetailsIconImageView; +@property (weak, nonatomic) IBOutlet UIImageView *badgeImageView; /** The room preview data may be used when mxRoom instance is not available diff --git a/Riot/Modules/Room/Views/Title/RoomTitleView.m b/Riot/Modules/Room/Views/Title/RoomTitleView.m index 898103702f..8e73295644 100644 --- a/Riot/Modules/Room/Views/Title/RoomTitleView.m +++ b/Riot/Modules/Room/Views/Title/RoomTitleView.m @@ -37,6 +37,8 @@ - (void)awakeFromNib { [super awakeFromNib]; + self.badgeImageView.image = nil; + if (_titleMask) { UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(reportTapGesture:)]; diff --git a/Riot/Modules/Room/Views/Title/RoomTitleView.xib b/Riot/Modules/Room/Views/Title/RoomTitleView.xib index 92c0c947c1..6b4b0722d7 100644 --- a/Riot/Modules/Room/Views/Title/RoomTitleView.xib +++ b/Riot/Modules/Room/Views/Title/RoomTitleView.xib @@ -1,11 +1,11 @@ - - + + - + @@ -15,8 +15,16 @@ + + + + + + + + - + @@ -26,7 +34,7 @@ - + @@ -34,11 +42,11 @@ - + - + @@ -55,14 +63,18 @@ + + + + @@ -70,9 +82,11 @@ + - + + From ba7ad7b9c77c5c62cd4883c3cf24d3026e73c358 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 8 Apr 2020 15:07:24 +0200 Subject: [PATCH 261/282] RoomVC: Handle title view encryption trust level badge image --- Riot/Modules/Room/RoomViewController.m | 42 ++++++++++++-------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index cb65bd1728..781970e126 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -1252,6 +1252,13 @@ - (void)sendTextMessage:(NSString*)msgTxt [self cancelEventSelection]; } +- (void)setRoomTitleViewClass:(Class)roomTitleViewClass +{ + [super setRoomTitleViewClass:roomTitleViewClass]; + + [self updateTitleViewEncryptionDecoration]; +} + - (void)destroy { rightBarButtonItems = nil; @@ -1630,34 +1637,13 @@ - (void)updateInputToolBarViewHeight - (UIImage*)roomEncryptionBadgeImage { - NSString *encryptionIconName; UIImage *encryptionIcon; if (self.isEncryptionEnabled) { RoomEncryptionTrustLevel roomEncryptionTrustLevel = ((RoomDataSource*)self.roomDataSource).encryptionTrustLevel; - switch (roomEncryptionTrustLevel) { - case RoomEncryptionTrustLevelWarning: - encryptionIconName = @"encryption_warning"; - break; - case RoomEncryptionTrustLevelNormal: - encryptionIconName = @"encryption_normal"; - break; - case RoomEncryptionTrustLevelTrusted: - encryptionIconName = @"encryption_trusted"; - break; - case RoomEncryptionTrustLevelUnknown: - encryptionIconName = @"encryption_normal"; - break; - default: - break; - } - } - - if (encryptionIconName) - { - encryptionIcon = [UIImage imageNamed:encryptionIconName]; + encryptionIcon = [EncryptionTrustLevelBadgeImageHelper roomBadgeImageFor:roomEncryptionTrustLevel]; } return encryptionIcon; @@ -1680,6 +1666,17 @@ - (void)updateExpandedHeaderEncryptionDecoration } } +- (void)updateTitleViewEncryptionDecoration +{ + if (![self.titleView isKindOfClass:[RoomTitleView class]]) + { + return; + } + + RoomTitleView *roomTitleView = (RoomTitleView*)self.titleView; + roomTitleView.badgeImageView.image = self.roomEncryptionBadgeImage; +} + - (void)updateEncryptionDecorationForRoomInputToolbar:(RoomInputToolbarView*)roomInputToolbarView { roomInputToolbarView.isEncryptionEnabled = self.isEncryptionEnabled; @@ -3271,6 +3268,7 @@ - (void)roomDataSource:(RoomDataSource *)roomDataSource didUpdateEncryptionTrust { [self updateInputToolbarEncryptionDecoration]; [self updateExpandedHeaderEncryptionDecoration]; + [self updateTitleViewEncryptionDecoration]; } #pragma mark - Segues From c72478c0a8e440c679e31ab4329b346149f2bfc4 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 8 Apr 2020 15:10:36 +0200 Subject: [PATCH 262/282] Update changes --- CHANGES.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.rst b/CHANGES.rst index 4538e7c432..a41e3cfedf 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -13,6 +13,7 @@ Improvements: * Verification by DM: Support QR code (#2921). * Cross-Signing: Detect and expose new sign-ins (#2918). * Cross-signing: Complete security at the end of sign in process( #3003). + * Make decoration uniform (#2972). Changes in 0.10.5 (2020-04-01) =============================================== From 5da191c4e7594ff68d75af4870b74872c473cb74 Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 8 Apr 2020 14:09:55 +0200 Subject: [PATCH 263/282] Key Backup: Add a screen for recovering with the local private key --- Riot.xcodeproj/project.pbxproj | 40 +++++ Riot/Assets/en.lproj/Vector.strings | 3 + Riot/Generated/Storyboards.swift | 5 + Riot/Generated/Strings.swift | 4 + .../Recover/KeyBackupRecoverCoordinator.swift | 27 ++- ...ackupRecoverDataLoadingViewModelType.swift | 37 ++++ ...ckupRecoverFromPrivateKeyCoordinator.swift | 69 ++++++++ ...RecoverFromPrivateKeyCoordinatorType.swift | 29 ++++ ...ackupRecoverFromPrivateKeyViewAction.swift | 25 +++ ...verFromPrivateKeyViewController.storyboard | 97 +++++++++++ ...pRecoverFromPrivateKeyViewController.swift | 158 ++++++++++++++++++ ...BackupRecoverFromPrivateKeyViewModel.swift | 89 ++++++++++ ...upRecoverFromPrivateKeyViewModelType.swift | 37 ++++ ...BackupRecoverFromPrivateKeyViewState.swift | 26 +++ ...eyBackupRecoverSuccessViewController.swift | 4 +- 15 files changed, 645 insertions(+), 5 deletions(-) create mode 100644 Riot/Modules/KeyBackup/Recover/Loading/KeyBackupRecoverDataLoadingViewModelType.swift create mode 100644 Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyCoordinator.swift create mode 100644 Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyCoordinatorType.swift create mode 100644 Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewAction.swift create mode 100644 Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewController.storyboard create mode 100644 Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewController.swift create mode 100644 Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModel.swift create mode 100644 Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModelType.swift create mode 100644 Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewState.swift diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 86c886df88..2501eb67cf 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -57,6 +57,14 @@ 324A2054225FC571004FE8B0 /* DeviceVerificationIncomingCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 324A204C225FC571004FE8B0 /* DeviceVerificationIncomingCoordinatorType.swift */; }; 324A2055225FC571004FE8B0 /* DeviceVerificationIncomingViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 324A204D225FC571004FE8B0 /* DeviceVerificationIncomingViewModelType.swift */; }; 324A2056225FC571004FE8B0 /* DeviceVerificationIncomingCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 324A204E225FC571004FE8B0 /* DeviceVerificationIncomingCoordinator.swift */; }; + 32607D6C243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32607D64243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewController.swift */; }; + 32607D6D243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32607D65243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyCoordinatorType.swift */; }; + 32607D6E243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32607D66243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewModelType.swift */; }; + 32607D6F243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 32607D67243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewController.storyboard */; }; + 32607D70243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32607D68243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewModel.swift */; }; + 32607D71243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32607D69243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewState.swift */; }; + 32607D72243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32607D6A243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewAction.swift */; }; + 32607D73243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32607D6B243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyCoordinator.swift */; }; 3275FD8C21A5A2C500B9C13D /* TermsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3275FD8B21A5A2C500B9C13D /* TermsView.swift */; }; 3281BCF72201FA4200F4A383 /* UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3281BCF62201FA4200F4A383 /* UIControl.swift */; }; 3284A35120A07C210044F922 /* postMessageAPI.js in Resources */ = {isa = PBXBuildFile; fileRef = 3284A35020A07C210044F922 /* postMessageAPI.js */; }; @@ -787,6 +795,14 @@ 325789A5237AB241009388E6 /* cy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cy; path = cy.lproj/InfoPlist.strings; sourceTree = ""; }; 325789A6237AB27F009388E6 /* cy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cy; path = cy.lproj/Localizable.strings; sourceTree = ""; }; 325789A7237AB297009388E6 /* cy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cy; path = cy.lproj/Vector.strings; sourceTree = ""; }; + 32607D64243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPrivateKeyViewController.swift; sourceTree = ""; }; + 32607D65243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPrivateKeyCoordinatorType.swift; sourceTree = ""; }; + 32607D66243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPrivateKeyViewModelType.swift; sourceTree = ""; }; + 32607D67243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = KeyBackupRecoverFromPrivateKeyViewController.storyboard; sourceTree = ""; }; + 32607D68243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPrivateKeyViewModel.swift; sourceTree = ""; }; + 32607D69243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPrivateKeyViewState.swift; sourceTree = ""; }; + 32607D6A243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPrivateKeyViewAction.swift; sourceTree = ""; }; + 32607D6B243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPrivateKeyCoordinator.swift; sourceTree = ""; }; 3267EFB320E379FD00FF1CAA /* CHANGES.rst */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CHANGES.rst; sourceTree = ""; }; 3267EFB420E379FD00FF1CAA /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; fileEncoding = 4; path = Podfile; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 3267EFB520E379FD00FF1CAA /* AUTHORS.rst */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = AUTHORS.rst; sourceTree = ""; }; @@ -1848,6 +1864,21 @@ path = Incoming; sourceTree = ""; }; + 32607D63243E0A55006674CC /* PrivateKey */ = { + isa = PBXGroup; + children = ( + 32607D64243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewController.swift */, + 32607D65243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyCoordinatorType.swift */, + 32607D66243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewModelType.swift */, + 32607D67243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewController.storyboard */, + 32607D68243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewModel.swift */, + 32607D69243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewState.swift */, + 32607D6A243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewAction.swift */, + 32607D6B243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyCoordinator.swift */, + ); + path = PrivateKey; + sourceTree = ""; + }; 32863A572384070300D07C4A /* Shared */ = { isa = PBXGroup; children = ( @@ -4074,6 +4105,7 @@ B19EFA3821F8BB2C00FC070E /* KeyBackupRecoverCoordinatorType.swift */, B19EFA3A21F8BB4100FC070E /* KeyBackupRecoverCoordinator.swift */, B140B4A721F8AB4600E3F5FE /* KeyBackupRecoverCoordinatorBridgePresenter.swift */, + 32607D63243E0A55006674CC /* PrivateKey */, B1E5368A21FB6FC0001F3AFF /* Passphrase */, B14F142522144F6400FA0595 /* RecoveryKey */, B1107EC62200B0190038014B /* Success */, @@ -4544,6 +4576,7 @@ 3232AB1522564D9100AD6A5C /* flat-swift4-vector.stencil in Resources */, F083BDE61E7009ED00A9B29C /* busy.mp3 in Resources */, B1B5574C20EE6C4D00210D55 /* MediaAlbumContentViewController.xib in Resources */, + 32607D6F243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewController.storyboard in Resources */, B1B557E820EF60F500210D55 /* MessagesSearchResultTextMsgBubbleCell.xib in Resources */, B1B558D920EF768F00210D55 /* RoomOutgoingEncryptedAttachmentBubbleCell.xib in Resources */, B1B5573020EE6C4D00210D55 /* BugReportViewController.xib in Resources */, @@ -4823,6 +4856,7 @@ buildActionMask = 2147483647; files = ( B1B557D120EF5E3500210D55 /* MediaAlbumTableCell.m in Sources */, + 32607D71243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewState.swift in Sources */, 324A2053225FC571004FE8B0 /* DeviceVerificationIncomingViewModel.swift in Sources */, B1B557A120EF58AD00210D55 /* ContactTableViewCell.m in Sources */, B1CE83DE2422817200D07506 /* KeyVerificationVerifyBySASViewModelType.swift in Sources */, @@ -4847,6 +4881,7 @@ B1B5598820EFC3E000210D55 /* WidgetManager.m in Sources */, B1DB4F0E22316FFF0065DBFA /* UserNameColorGenerator.swift in Sources */, B157FAA123264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsCoordinatorType.swift in Sources */, + 32607D6C243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewController.swift in Sources */, B1057789221304EC00334B1E /* KeyBackupSetupSuccessFromPassphraseViewController.swift in Sources */, B1DCC61922E5E17100625807 /* EmojiPickerCoordinatorType.swift in Sources */, B1C3360122F1ED600021BA8D /* MediaPickerCoordinatorType.swift in Sources */, @@ -4863,6 +4898,7 @@ B1BD71C1238EA92100BA92E2 /* WidgetPermissionViewModel.swift in Sources */, B1B557E320EF60B900210D55 /* MessagesSearchResultAttachmentBubbleCell.m in Sources */, B1CE9F062216FB09000FAE6A /* EncryptionKeysExportPresenter.swift in Sources */, + 32607D72243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewAction.swift in Sources */, B1B5574420EE6C4D00210D55 /* CallViewController.m in Sources */, B12D7A0023E2462200FACEDC /* UserVerificationStartCoordinatorType.swift in Sources */, B1B5572220EE6C4D00210D55 /* RoomSettingsViewController.m in Sources */, @@ -4874,6 +4910,7 @@ B1B558E820EF768F00210D55 /* RoomIncomingAttachmentWithPaginationTitleBubbleCell.m in Sources */, B1B558F320EF768F00210D55 /* RoomOutgoingAttachmentWithoutSenderInfoBubbleCell.m in Sources */, B12D79FE23E2462200FACEDC /* UserVerificationStartViewController.swift in Sources */, + 32607D73243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyCoordinator.swift in Sources */, B1B557BD20EF5B4500210D55 /* KeyboardGrowingTextView.m in Sources */, B1A68593229E807A00D6C09A /* RoomBubbleCellLayout.swift in Sources */, B1B558F420EF768F00210D55 /* RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */, @@ -4890,6 +4927,7 @@ B1DCC61C22E5E17100625807 /* EmojiPickerViewAction.swift in Sources */, B1098BDF21ECE09F000DDA48 /* Strings.swift in Sources */, B1BEE71523DF2ACF0003A4CB /* UserVerificationCoordinator.swift in Sources */, + 32607D70243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewModel.swift in Sources */, B1B558C420EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */, B1963B2E228F1C4900CBA17F /* BubbleReactionViewData.swift in Sources */, B1C3361C22F32B4A0021BA8D /* SingleImagePickerPresenter.swift in Sources */, @@ -5156,6 +5194,7 @@ B14F143322144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewModel.swift in Sources */, B1B336C0242B933700F95EC4 /* KeyVerificationSelfVerifyStartViewModelType.swift in Sources */, 32A6001822C661100042C1D9 /* EditHistoryViewModel.swift in Sources */, + 32607D6E243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewModelType.swift in Sources */, B1B558D020EF768F00210D55 /* RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */, B1B558CF20EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.m in Sources */, B140B4A221F87F7100E3F5FE /* OperationQueue.swift in Sources */, @@ -5281,6 +5320,7 @@ B12D79FF23E2462200FACEDC /* UserVerificationStartViewState.swift in Sources */, B1B558CE20EF768F00210D55 /* RoomOutgoingEncryptedAttachmentBubbleCell.m in Sources */, B1B5577D20EE84BF00210D55 /* CircleButton.m in Sources */, + 32607D6D243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyCoordinatorType.swift in Sources */, 32BF995521FA2AB700698084 /* SettingsKeyBackupViewAction.swift in Sources */, B109D6F1222D8C400061B6D9 /* UIApplication.swift in Sources */, B1BEE73723DF44A60003A4CB /* UserVerificationSessionsStatusViewState.swift in Sources */, diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 2c84b2de8f..0e536c1fd0 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -951,6 +951,9 @@ "key_backup_recover_invalid_recovery_key_title" = "Recovery Key Mismatch"; "key_backup_recover_invalid_recovery_key" = "Backup could not be decrypted with this key: please verify that you entered the correct recovery key."; +// Recover from private key +"key_backup_recover_from_private_key_info" = "Restoring backup…"; + // Recover from passphrase "key_backup_recover_from_passphrase_info" = "Use your recovery passphrase to unlock your secure message history"; diff --git a/Riot/Generated/Storyboards.swift b/Riot/Generated/Storyboards.swift index fc80cf7bad..69b27dd662 100644 --- a/Riot/Generated/Storyboards.swift +++ b/Riot/Generated/Storyboards.swift @@ -37,6 +37,11 @@ internal enum StoryboardScene { internal static let initialScene = InitialSceneType(storyboard: KeyBackupRecoverFromPassphraseViewController.self) } + internal enum KeyBackupRecoverFromPrivateKeyViewController: StoryboardType { + internal static let storyboardName = "KeyBackupRecoverFromPrivateKeyViewController" + + internal static let initialScene = InitialSceneType(storyboard: KeyBackupRecoverFromPrivateKeyViewController.self) + } internal enum KeyBackupRecoverFromRecoveryKeyViewController: StoryboardType { internal static let storyboardName = "KeyBackupRecoverFromRecoveryKeyViewController" diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index b9f23f4fa5..13e0da4c0e 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -1330,6 +1330,10 @@ internal enum VectorL10n { internal static var keyBackupRecoverFromPassphraseRecoverAction: String { return VectorL10n.tr("Vector", "key_backup_recover_from_passphrase_recover_action") } + /// Restoring backup… + internal static var keyBackupRecoverFromPrivateKeyInfo: String { + return VectorL10n.tr("Vector", "key_backup_recover_from_private_key_info") + } /// Use your recovery key to unlock your secure message history internal static var keyBackupRecoverFromRecoveryKeyInfo: String { return VectorL10n.tr("Vector", "key_backup_recover_from_recovery_key_info") diff --git a/Riot/Modules/KeyBackup/Recover/KeyBackupRecoverCoordinator.swift b/Riot/Modules/KeyBackup/Recover/KeyBackupRecoverCoordinator.swift index 3912c1715d..4bff0e7a80 100644 --- a/Riot/Modules/KeyBackup/Recover/KeyBackupRecoverCoordinator.swift +++ b/Riot/Modules/KeyBackup/Recover/KeyBackupRecoverCoordinator.swift @@ -43,11 +43,15 @@ final class KeyBackupRecoverCoordinator: KeyBackupRecoverCoordinatorType { // MARK: - Public func start() { - + let rootCoordinator: Coordinator & Presentable + // Check if we have the private key locally + if self.session.crypto.backup.hasPrivateKeyInCryptoStore { + rootCoordinator = createRecoverFromPrivateKeyCoordinator() + } // Check if a passphrase has been set for given backup - if let megolmBackupAuthData = MXMegolmBackupAuthData(fromJSON: self.keyBackupVersion.authData), megolmBackupAuthData.privateKeySalt != nil { + else if let megolmBackupAuthData = MXMegolmBackupAuthData(fromJSON: self.keyBackupVersion.authData), megolmBackupAuthData.privateKeySalt != nil { rootCoordinator = self.createRecoverFromPassphraseCoordinator() } else { rootCoordinator = self.createRecoverFromRecoveryKeyCoordinator() @@ -66,6 +70,12 @@ final class KeyBackupRecoverCoordinator: KeyBackupRecoverCoordinatorType { // MARK: - Private + private func createRecoverFromPrivateKeyCoordinator() -> KeyBackupRecoverFromPrivateKeyCoordinator { + let coordinator = KeyBackupRecoverFromPrivateKeyCoordinator(keyBackup: self.session.crypto.backup, keyBackupVersion: self.keyBackupVersion) + coordinator.delegate = self + return coordinator + } + private func createRecoverFromPassphraseCoordinator() -> KeyBackupRecoverFromPassphraseCoordinator { let coordinator = KeyBackupRecoverFromPassphraseCoordinator(keyBackup: self.session.crypto.backup, keyBackupVersion: self.keyBackupVersion) coordinator.delegate = self @@ -97,6 +107,17 @@ final class KeyBackupRecoverCoordinator: KeyBackupRecoverCoordinatorType { } } +// MARK: - KeyBackupRecoverFromPassphraseCoordinatorDelegate +extension KeyBackupRecoverCoordinator: KeyBackupRecoverFromPrivateKeyCoordinatorDelegate { + func keyBackupRecoverFromPrivateKeyCoordinatorDidRecover(_ coordinator: KeyBackupRecoverFromPrivateKeyCoordinatorType) { + self.showRecoverSuccess() + } + + func keyBackupRecoverFromPrivateKeyCoordinatorDidCancel(_ coordinator: KeyBackupRecoverFromPrivateKeyCoordinatorType) { + self.delegate?.keyBackupRecoverCoordinatorDidCancel(self) + } +} + // MARK: - KeyBackupRecoverFromPassphraseCoordinatorDelegate extension KeyBackupRecoverCoordinator: KeyBackupRecoverFromPassphraseCoordinatorDelegate { func keyBackupRecoverFromPassphraseCoordinatorDidRecover(_ keyBackupRecoverFromPassphraseCoordinator: KeyBackupRecoverFromPassphraseCoordinatorType) { @@ -125,7 +146,7 @@ extension KeyBackupRecoverCoordinator: KeyBackupRecoverFromRecoveryKeyCoordinato // MARK: - KeyBackupRecoverSuccessViewControllerDelegate extension KeyBackupRecoverCoordinator: KeyBackupRecoverSuccessViewControllerDelegate { - func KeyBackupRecoverSuccessViewControllerDidTapDone(_ keyBackupRecoverSuccessViewController: KeyBackupRecoverSuccessViewController) { + func keyBackupRecoverSuccessViewControllerDidTapDone(_ keyBackupRecoverSuccessViewController: KeyBackupRecoverSuccessViewController) { self.delegate?.keyBackupRecoverCoordinatorDidRecover(self) } } diff --git a/Riot/Modules/KeyBackup/Recover/Loading/KeyBackupRecoverDataLoadingViewModelType.swift b/Riot/Modules/KeyBackup/Recover/Loading/KeyBackupRecoverDataLoadingViewModelType.swift new file mode 100644 index 0000000000..1ef8aab16b --- /dev/null +++ b/Riot/Modules/KeyBackup/Recover/Loading/KeyBackupRecoverDataLoadingViewModelType.swift @@ -0,0 +1,37 @@ +// File created from ScreenTemplate +// $ createScreen.sh KeyBackup/Recover/Loading KeyBackupRecoverDataLoading +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol KeyBackupRecoverDataLoadingViewModelViewDelegate: class { + func keyBackupRecoverDataLoadingViewModel(_ viewModel: KeyBackupRecoverDataLoadingViewModelType, didUpdateViewState viewSate: KeyBackupRecoverDataLoadingViewState) +} + +protocol KeyBackupRecoverDataLoadingViewModelCoordinatorDelegate: class { + func keyBackupRecoverDataLoadingViewModelDidRecover(_ viewModel: KeyBackupRecoverDataLoadingViewModelType) + func keyBackupRecoverDataLoadingViewModelDidCancel(_ viewModel: KeyBackupRecoverDataLoadingViewModelType) +} + +/// Protocol describing the view model used by `KeyBackupRecoverDataLoadingViewController` +protocol KeyBackupRecoverDataLoadingViewModelType { + + var viewDelegate: KeyBackupRecoverDataLoadingViewModelViewDelegate? { get set } + var coordinatorDelegate: KeyBackupRecoverDataLoadingViewModelCoordinatorDelegate? { get set } + + func process(viewAction: KeyBackupRecoverDataLoadingViewAction) +} diff --git a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyCoordinator.swift b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyCoordinator.swift new file mode 100644 index 0000000000..2512039e06 --- /dev/null +++ b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyCoordinator.swift @@ -0,0 +1,69 @@ +// File created from ScreenTemplate +// $ createScreen.sh .KeyBackup/Recover/PrivateKey KeyBackupRecoverFromPrivateKey +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation +import UIKit + +final class KeyBackupRecoverFromPrivateKeyCoordinator: KeyBackupRecoverFromPrivateKeyCoordinatorType { + + // MARK: - Properties + + // MARK: Private + + private var keyBackupRecoverFromPrivateKeyViewModel: KeyBackupRecoverFromPrivateKeyViewModelType + private let keyBackupRecoverFromPrivateKeyViewController: KeyBackupRecoverFromPrivateKeyViewController + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: KeyBackupRecoverFromPrivateKeyCoordinatorDelegate? + + // MARK: - Setup + + init(keyBackup: MXKeyBackup, keyBackupVersion: MXKeyBackupVersion) { + + let keyBackupRecoverFromPrivateKeyViewModel = KeyBackupRecoverFromPrivateKeyViewModel(keyBackup: keyBackup, keyBackupVersion: keyBackupVersion) + let keyBackupRecoverFromPrivateKeyViewController = KeyBackupRecoverFromPrivateKeyViewController.instantiate(with: keyBackupRecoverFromPrivateKeyViewModel) + self.keyBackupRecoverFromPrivateKeyViewModel = keyBackupRecoverFromPrivateKeyViewModel + self.keyBackupRecoverFromPrivateKeyViewController = keyBackupRecoverFromPrivateKeyViewController + } + + // MARK: - Public methods + + func start() { + self.keyBackupRecoverFromPrivateKeyViewModel.coordinatorDelegate = self + } + + func toPresentable() -> UIViewController { + return self.keyBackupRecoverFromPrivateKeyViewController + } +} + +// MARK: - KeyBackupRecoverFromPrivateKeyViewModelCoordinatorDelegate +extension KeyBackupRecoverFromPrivateKeyCoordinator: KeyBackupRecoverFromPrivateKeyViewModelCoordinatorDelegate { + + func keyBackupRecoverFromPrivateKeyViewModelDidRecover(_ viewModel: KeyBackupRecoverFromPrivateKeyViewModelType) { + self.delegate?.keyBackupRecoverFromPrivateKeyCoordinatorDidRecover(self) + } + + func keyBackupRecoverFromPrivateKeyViewModelDidCancel(_ viewModel: KeyBackupRecoverFromPrivateKeyViewModelType) { + self.delegate?.keyBackupRecoverFromPrivateKeyCoordinatorDidCancel(self) + } +} diff --git a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyCoordinatorType.swift b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyCoordinatorType.swift new file mode 100644 index 0000000000..b50aa96679 --- /dev/null +++ b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyCoordinatorType.swift @@ -0,0 +1,29 @@ +// File created from ScreenTemplate +// $ createScreen.sh .KeyBackup/Recover/PrivateKey KeyBackupRecoverFromPrivateKey +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol KeyBackupRecoverFromPrivateKeyCoordinatorDelegate: class { + func keyBackupRecoverFromPrivateKeyCoordinatorDidRecover(_ coordinator: KeyBackupRecoverFromPrivateKeyCoordinatorType) + func keyBackupRecoverFromPrivateKeyCoordinatorDidCancel(_ coordinator: KeyBackupRecoverFromPrivateKeyCoordinatorType) +} + +/// `KeyBackupRecoverFromPrivateKeyCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow. +protocol KeyBackupRecoverFromPrivateKeyCoordinatorType: Coordinator, Presentable { + var delegate: KeyBackupRecoverFromPrivateKeyCoordinatorDelegate? { get } +} diff --git a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewAction.swift b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewAction.swift new file mode 100644 index 0000000000..5ad5a61bf3 --- /dev/null +++ b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewAction.swift @@ -0,0 +1,25 @@ +// File created from ScreenTemplate +// $ createScreen.sh .KeyBackup/Recover/PrivateKey KeyBackupRecoverFromPrivateKey +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// KeyBackupRecoverFromPrivateKeyViewController view actions exposed to view model +enum KeyBackupRecoverFromPrivateKeyViewAction { + case recover + case cancel +} diff --git a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewController.storyboard b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewController.storyboard new file mode 100644 index 0000000000..1c8ba341c8 --- /dev/null +++ b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewController.storyboard @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewController.swift b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewController.swift new file mode 100644 index 0000000000..1aaf96e62f --- /dev/null +++ b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewController.swift @@ -0,0 +1,158 @@ +// File created from ScreenTemplate +// $ createScreen.sh .KeyBackup/Recover/PrivateKey KeyBackupRecoverFromPrivateKey +/* + Copyright 2020 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 KeyBackupRecoverFromPrivateKeyViewController: UIViewController { + + // MARK: - Constants + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var shieldImageView: UIImageView! + + @IBOutlet private weak var informationLabel: UILabel! + + // MARK: Private + + private var viewModel: KeyBackupRecoverFromPrivateKeyViewModelType! + private var theme: Theme! + private var errorPresenter: MXKErrorPresentation! + private var activityPresenter: ActivityIndicatorPresenter! + + // MARK: - Setup + + class func instantiate(with viewModel: KeyBackupRecoverFromPrivateKeyViewModelType) -> KeyBackupRecoverFromPrivateKeyViewController { + let viewController = StoryboardScene.KeyBackupRecoverFromPrivateKeyViewController.initialScene.instantiate() + viewController.viewModel = viewModel + viewController.theme = ThemeService.shared().theme + return viewController + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + self.title = VectorL10n.keyBackupRecoverTitle + + self.setupViews() + self.activityPresenter = ActivityIndicatorPresenter() + self.errorPresenter = MXKErrorAlertPresentation() + + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + + self.viewModel.viewDelegate = self + + self.viewModel.process(viewAction: .recover) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + } + + 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() { + let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in + self?.cancelButtonAction() + } + self.navigationItem.rightBarButtonItem = cancelBarButtonItem + + let shieldImage = Asset.Images.keyBackupLogo.image.withRenderingMode(.alwaysTemplate) + self.shieldImageView.image = shieldImage + + self.informationLabel.text = VectorL10n.keyBackupRecoverFromPrivateKeyInfo + } + + private func update(theme: Theme) { + self.theme = theme + + self.view.backgroundColor = theme.headerBackgroundColor + + if let navigationBar = self.navigationController?.navigationBar { + theme.applyStyle(onNavigationBar: navigationBar) + } + + self.shieldImageView.tintColor = theme.textPrimaryColor + + self.informationLabel.textColor = theme.textPrimaryColor + } + + private func render(viewState: KeyBackupRecoverFromPrivateKeyViewState) { + switch viewState { + case .loading: + self.renderLoading() + case .loaded: + self.renderLoaded() + case .error(let error): + self.render(error: error) + } + } + + private func renderLoading() { + self.activityPresenter.presentActivityIndicator(on: self.view, animated: true) + } + + private func renderLoaded() { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + } + + private func render(error: Error) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil) + } + + + // MARK: - Actions + + private func cancelButtonAction() { + self.viewModel.process(viewAction: .cancel) + } +} + + +// MARK: - KeyBackupRecoverFromPrivateKeyViewModelViewDelegate +extension KeyBackupRecoverFromPrivateKeyViewController: KeyBackupRecoverFromPrivateKeyViewModelViewDelegate { + + func keyBackupRecoverFromPrivateKeyViewModel(_ viewModel: KeyBackupRecoverFromPrivateKeyViewModelType, didUpdateViewState viewSate: KeyBackupRecoverFromPrivateKeyViewState) { + self.render(viewState: viewSate) + } +} diff --git a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModel.swift b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModel.swift new file mode 100644 index 0000000000..6e697b6123 --- /dev/null +++ b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModel.swift @@ -0,0 +1,89 @@ +// File created from ScreenTemplate +// $ createScreen.sh .KeyBackup/Recover/PrivateKey KeyBackupRecoverFromPrivateKey +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +final class KeyBackupRecoverFromPrivateKeyViewModel: KeyBackupRecoverFromPrivateKeyViewModelType { + + // MARK: - Properties + + // MARK: Private + + private let keyBackup: MXKeyBackup + private var currentHTTPOperation: MXHTTPOperation? + private let keyBackupVersion: MXKeyBackupVersion + + // MARK: Public + + weak var viewDelegate: KeyBackupRecoverFromPrivateKeyViewModelViewDelegate? + weak var coordinatorDelegate: KeyBackupRecoverFromPrivateKeyViewModelCoordinatorDelegate? + + // MARK: - Setup + + init(keyBackup: MXKeyBackup, keyBackupVersion: MXKeyBackupVersion) { + self.keyBackup = keyBackup + self.keyBackupVersion = keyBackupVersion + } + + deinit { + } + + // MARK: - Public + + func process(viewAction: KeyBackupRecoverFromPrivateKeyViewAction) { + switch viewAction { + case .recover: + self.recoverWithPrivateKey() + case .cancel: + self.coordinatorDelegate?.keyBackupRecoverFromPrivateKeyViewModelDidCancel(self) + } + } + + // MARK: - Private + + private func recoverWithPrivateKey() { + + self.update(viewState: .loading) + + self.currentHTTPOperation = keyBackup.restoreUsingPrivateKey(inCryptoStore: keyBackupVersion, room: nil, session: nil, success: { [weak self] (_, _) in + guard let sself = self else { + return + } + + // Trust on decrypt + sself.currentHTTPOperation = sself.keyBackup.trust(sself.keyBackupVersion, trust: true, success: { [weak sself] () in + guard let ssself = sself else { + return + } + + ssself.update(viewState: .loaded) + ssself.coordinatorDelegate?.keyBackupRecoverFromPrivateKeyViewModelDidRecover(ssself) + + }, failure: { [weak sself] error in + sself?.update(viewState: .error(error)) + }) + + }, failure: { [weak self] error in + self?.update(viewState: .error(error)) + }) + } + + private func update(viewState: KeyBackupRecoverFromPrivateKeyViewState) { + self.viewDelegate?.keyBackupRecoverFromPrivateKeyViewModel(self, didUpdateViewState: viewState) + } +} diff --git a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModelType.swift b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModelType.swift new file mode 100644 index 0000000000..ed030362fc --- /dev/null +++ b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModelType.swift @@ -0,0 +1,37 @@ +// File created from ScreenTemplate +// $ createScreen.sh .KeyBackup/Recover/PrivateKey KeyBackupRecoverFromPrivateKey +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol KeyBackupRecoverFromPrivateKeyViewModelViewDelegate: class { + func keyBackupRecoverFromPrivateKeyViewModel(_ viewModel: KeyBackupRecoverFromPrivateKeyViewModelType, didUpdateViewState viewSate: KeyBackupRecoverFromPrivateKeyViewState) +} + +protocol KeyBackupRecoverFromPrivateKeyViewModelCoordinatorDelegate: class { + func keyBackupRecoverFromPrivateKeyViewModelDidRecover(_ viewModel: KeyBackupRecoverFromPrivateKeyViewModelType) + func keyBackupRecoverFromPrivateKeyViewModelDidCancel(_ viewModel: KeyBackupRecoverFromPrivateKeyViewModelType) +} + +/// Protocol describing the view model used by `KeyBackupRecoverFromPrivateKeyViewController` +protocol KeyBackupRecoverFromPrivateKeyViewModelType { + + var viewDelegate: KeyBackupRecoverFromPrivateKeyViewModelViewDelegate? { get set } + var coordinatorDelegate: KeyBackupRecoverFromPrivateKeyViewModelCoordinatorDelegate? { get set } + + func process(viewAction: KeyBackupRecoverFromPrivateKeyViewAction) +} diff --git a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewState.swift b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewState.swift new file mode 100644 index 0000000000..fcfe3afa2b --- /dev/null +++ b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewState.swift @@ -0,0 +1,26 @@ +// File created from ScreenTemplate +// $ createScreen.sh .KeyBackup/Recover/PrivateKey KeyBackupRecoverFromPrivateKey +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// KeyBackupRecoverFromPrivateKeyViewController view state +enum KeyBackupRecoverFromPrivateKeyViewState { + case loading + case loaded + case error(Error) +} diff --git a/Riot/Modules/KeyBackup/Recover/Success/KeyBackupRecoverSuccessViewController.swift b/Riot/Modules/KeyBackup/Recover/Success/KeyBackupRecoverSuccessViewController.swift index 8d126897db..dd5a834daa 100644 --- a/Riot/Modules/KeyBackup/Recover/Success/KeyBackupRecoverSuccessViewController.swift +++ b/Riot/Modules/KeyBackup/Recover/Success/KeyBackupRecoverSuccessViewController.swift @@ -17,7 +17,7 @@ import UIKit protocol KeyBackupRecoverSuccessViewControllerDelegate: class { - func KeyBackupRecoverSuccessViewControllerDidTapDone(_ keyBackupRecoverSuccessViewController: KeyBackupRecoverSuccessViewController) + func keyBackupRecoverSuccessViewControllerDidTapDone(_ keyBackupRecoverSuccessViewController: KeyBackupRecoverSuccessViewController) } final class KeyBackupRecoverSuccessViewController: UIViewController { @@ -115,6 +115,6 @@ final class KeyBackupRecoverSuccessViewController: UIViewController { } @IBAction private func doneButtonAction(_ sender: Any) { - self.delegate?.KeyBackupRecoverSuccessViewControllerDidTapDone(self) + self.delegate?.keyBackupRecoverSuccessViewControllerDidTapDone(self) } } From d0f90558b25e35bcfc0368682a9bc8718b976008 Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 8 Apr 2020 16:48:12 +0200 Subject: [PATCH 264/282] Key Backup: Use legacy screens if the private key does not work --- .../Recover/KeyBackupRecoverCoordinator.swift | 38 +++++++++++++++---- ...ckupRecoverFromPrivateKeyCoordinator.swift | 5 ++- ...RecoverFromPrivateKeyCoordinatorType.swift | 1 + ...BackupRecoverFromPrivateKeyViewModel.swift | 13 ++++++- ...upRecoverFromPrivateKeyViewModelType.swift | 1 + 5 files changed, 48 insertions(+), 10 deletions(-) diff --git a/Riot/Modules/KeyBackup/Recover/KeyBackupRecoverCoordinator.swift b/Riot/Modules/KeyBackup/Recover/KeyBackupRecoverCoordinator.swift index 4bff0e7a80..88ff6d3753 100644 --- a/Riot/Modules/KeyBackup/Recover/KeyBackupRecoverCoordinator.swift +++ b/Riot/Modules/KeyBackup/Recover/KeyBackupRecoverCoordinator.swift @@ -48,15 +48,11 @@ final class KeyBackupRecoverCoordinator: KeyBackupRecoverCoordinatorType { // Check if we have the private key locally if self.session.crypto.backup.hasPrivateKeyInCryptoStore { - rootCoordinator = createRecoverFromPrivateKeyCoordinator() - } - // Check if a passphrase has been set for given backup - else if let megolmBackupAuthData = MXMegolmBackupAuthData(fromJSON: self.keyBackupVersion.authData), megolmBackupAuthData.privateKeySalt != nil { - rootCoordinator = self.createRecoverFromPassphraseCoordinator() + rootCoordinator = self.createRecoverFromPrivateKeyCoordinator() } else { - rootCoordinator = self.createRecoverFromRecoveryKeyCoordinator() + rootCoordinator = self.createRecoverWithUserInteractionCoordinator() } - + rootCoordinator.start() self.add(childCoordinator: rootCoordinator) @@ -70,6 +66,18 @@ final class KeyBackupRecoverCoordinator: KeyBackupRecoverCoordinatorType { // MARK: - Private + private func createRecoverWithUserInteractionCoordinator() -> Coordinator & Presentable { + let coordinator: Coordinator & Presentable + + // Check if a passphrase has been set for given backup + if let megolmBackupAuthData = MXMegolmBackupAuthData(fromJSON: self.keyBackupVersion.authData), megolmBackupAuthData.privateKeySalt != nil { + coordinator = self.createRecoverFromPassphraseCoordinator() + } else { + coordinator = self.createRecoverFromRecoveryKeyCoordinator() + } + return coordinator + } + private func createRecoverFromPrivateKeyCoordinator() -> KeyBackupRecoverFromPrivateKeyCoordinator { let coordinator = KeyBackupRecoverFromPrivateKeyCoordinator(keyBackup: self.session.crypto.backup, keyBackupVersion: self.keyBackupVersion) coordinator.delegate = self @@ -105,14 +113,30 @@ final class KeyBackupRecoverCoordinator: KeyBackupRecoverCoordinatorType { keyBackupRecoverSuccessViewController.delegate = self self.navigationRouter.push(keyBackupRecoverSuccessViewController, animated: true, popCompletion: nil) } + + private func showRecoverFallback() { + let coordinator = self.createRecoverWithUserInteractionCoordinator() + self.add(childCoordinator: coordinator) + + // Skip the previously displayed KeyBackupRecoverFromPrivateKeyCoordinator in the navigation stack + self.navigationRouter.setRootModule(coordinator) + + coordinator.start() + } } // MARK: - KeyBackupRecoverFromPassphraseCoordinatorDelegate extension KeyBackupRecoverCoordinator: KeyBackupRecoverFromPrivateKeyCoordinatorDelegate { + func keyBackupRecoverFromPrivateKeyCoordinatorDidRecover(_ coordinator: KeyBackupRecoverFromPrivateKeyCoordinatorType) { self.showRecoverSuccess() } + func keyBackupRecoverFromPrivateKeyCoordinatorDidPrivateKeyFail(_ coordinator: KeyBackupRecoverFromPrivateKeyCoordinatorType) { + // The private key did not work. Ask the user to enter their passphrase or recovery key + self.showRecoverFallback() + } + func keyBackupRecoverFromPrivateKeyCoordinatorDidCancel(_ coordinator: KeyBackupRecoverFromPrivateKeyCoordinatorType) { self.delegate?.keyBackupRecoverCoordinatorDidCancel(self) } diff --git a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyCoordinator.swift b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyCoordinator.swift index 2512039e06..f5b2d38316 100644 --- a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyCoordinator.swift +++ b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyCoordinator.swift @@ -58,11 +58,14 @@ final class KeyBackupRecoverFromPrivateKeyCoordinator: KeyBackupRecoverFromPriva // MARK: - KeyBackupRecoverFromPrivateKeyViewModelCoordinatorDelegate extension KeyBackupRecoverFromPrivateKeyCoordinator: KeyBackupRecoverFromPrivateKeyViewModelCoordinatorDelegate { - func keyBackupRecoverFromPrivateKeyViewModelDidRecover(_ viewModel: KeyBackupRecoverFromPrivateKeyViewModelType) { self.delegate?.keyBackupRecoverFromPrivateKeyCoordinatorDidRecover(self) } + func keyBackupRecoverFromPrivateKeyViewModelDidPrivateKeyFail(_ viewModel: KeyBackupRecoverFromPrivateKeyViewModelType) { + self.delegate?.keyBackupRecoverFromPrivateKeyCoordinatorDidPrivateKeyFail(self) + } + func keyBackupRecoverFromPrivateKeyViewModelDidCancel(_ viewModel: KeyBackupRecoverFromPrivateKeyViewModelType) { self.delegate?.keyBackupRecoverFromPrivateKeyCoordinatorDidCancel(self) } diff --git a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyCoordinatorType.swift b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyCoordinatorType.swift index b50aa96679..aab042ba55 100644 --- a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyCoordinatorType.swift +++ b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyCoordinatorType.swift @@ -20,6 +20,7 @@ import Foundation protocol KeyBackupRecoverFromPrivateKeyCoordinatorDelegate: class { func keyBackupRecoverFromPrivateKeyCoordinatorDidRecover(_ coordinator: KeyBackupRecoverFromPrivateKeyCoordinatorType) + func keyBackupRecoverFromPrivateKeyCoordinatorDidPrivateKeyFail(_ coordinator: KeyBackupRecoverFromPrivateKeyCoordinatorType) func keyBackupRecoverFromPrivateKeyCoordinatorDidCancel(_ coordinator: KeyBackupRecoverFromPrivateKeyCoordinatorType) } diff --git a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModel.swift b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModel.swift index 6e697b6123..5fd6e1440e 100644 --- a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModel.swift +++ b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModel.swift @@ -60,7 +60,7 @@ final class KeyBackupRecoverFromPrivateKeyViewModel: KeyBackupRecoverFromPrivate self.update(viewState: .loading) - self.currentHTTPOperation = keyBackup.restoreUsingPrivateKey(inCryptoStore: keyBackupVersion, room: nil, session: nil, success: { [weak self] (_, _) in + self.currentHTTPOperation = keyBackup.restore(usingPrivateKeyKeyBackup: keyBackupVersion, room: nil, session: nil, success: { [weak self] (_, _) in guard let sself = self else { return } @@ -79,7 +79,16 @@ final class KeyBackupRecoverFromPrivateKeyViewModel: KeyBackupRecoverFromPrivate }) }, failure: { [weak self] error in - self?.update(viewState: .error(error)) + guard let sself = self else { + return + } + + if (error as NSError).domain == MXKeyBackupErrorDomain + && (error as NSError).code == Int(MXKeyBackupErrorInvalidOrMissingLocalPrivateKey.rawValue) { + sself.coordinatorDelegate?.keyBackupRecoverFromPrivateKeyViewModelDidPrivateKeyFail(sself) + } else { + sself.update(viewState: .error(error)) + } }) } diff --git a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModelType.swift b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModelType.swift index ed030362fc..1188e0560c 100644 --- a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModelType.swift +++ b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModelType.swift @@ -24,6 +24,7 @@ protocol KeyBackupRecoverFromPrivateKeyViewModelViewDelegate: class { protocol KeyBackupRecoverFromPrivateKeyViewModelCoordinatorDelegate: class { func keyBackupRecoverFromPrivateKeyViewModelDidRecover(_ viewModel: KeyBackupRecoverFromPrivateKeyViewModelType) + func keyBackupRecoverFromPrivateKeyViewModelDidPrivateKeyFail(_ viewModel: KeyBackupRecoverFromPrivateKeyViewModelType) func keyBackupRecoverFromPrivateKeyViewModelDidCancel(_ viewModel: KeyBackupRecoverFromPrivateKeyViewModelType) } From 40b69f2d3ad7cdf8c0cd6827bf960af91de6d373 Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 8 Apr 2020 18:25:42 +0200 Subject: [PATCH 265/282] Update Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewState.swift Co-Authored-By: SBiOSoftWhare --- .../PrivateKey/KeyBackupRecoverFromPrivateKeyViewState.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewState.swift b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewState.swift index fcfe3afa2b..bdd4178534 100644 --- a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewState.swift +++ b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewState.swift @@ -1,7 +1,7 @@ // File created from ScreenTemplate // $ createScreen.sh .KeyBackup/Recover/PrivateKey KeyBackupRecoverFromPrivateKey /* - Copyright 2019 New Vector Ltd + Copyright 2020 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. From 021a154c3b1873a9dd0c06a316bcb6309503b439 Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 8 Apr 2020 18:29:37 +0200 Subject: [PATCH 266/282] Fix Steve's comment --- ...BackupRecoverFromPrivateKeyViewModel.swift | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModel.swift b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModel.swift index 5fd6e1440e..408a404dfb 100644 --- a/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModel.swift +++ b/Riot/Modules/KeyBackup/Recover/PrivateKey/KeyBackupRecoverFromPrivateKeyViewModel.swift @@ -61,33 +61,33 @@ final class KeyBackupRecoverFromPrivateKeyViewModel: KeyBackupRecoverFromPrivate self.update(viewState: .loading) self.currentHTTPOperation = keyBackup.restore(usingPrivateKeyKeyBackup: keyBackupVersion, room: nil, session: nil, success: { [weak self] (_, _) in - guard let sself = self else { + guard let self = self else { return } // Trust on decrypt - sself.currentHTTPOperation = sself.keyBackup.trust(sself.keyBackupVersion, trust: true, success: { [weak sself] () in - guard let ssself = sself else { + self.currentHTTPOperation = self.keyBackup.trust(self.keyBackupVersion, trust: true, success: { [weak self] () in + guard let self = self else { return } - ssself.update(viewState: .loaded) - ssself.coordinatorDelegate?.keyBackupRecoverFromPrivateKeyViewModelDidRecover(ssself) + self.update(viewState: .loaded) + self.coordinatorDelegate?.keyBackupRecoverFromPrivateKeyViewModelDidRecover(self) - }, failure: { [weak sself] error in - sself?.update(viewState: .error(error)) + }, failure: { [weak self] error in + self?.update(viewState: .error(error)) }) }, failure: { [weak self] error in - guard let sself = self else { + guard let self = self else { return } if (error as NSError).domain == MXKeyBackupErrorDomain && (error as NSError).code == Int(MXKeyBackupErrorInvalidOrMissingLocalPrivateKey.rawValue) { - sself.coordinatorDelegate?.keyBackupRecoverFromPrivateKeyViewModelDidPrivateKeyFail(sself) + self.coordinatorDelegate?.keyBackupRecoverFromPrivateKeyViewModelDidPrivateKeyFail(self) } else { - sself.update(viewState: .error(error)) + self.update(viewState: .error(error)) } }) } From 5724124d29fce3091befc457d29a4c951cad4292 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 9 Apr 2020 14:31:26 +0200 Subject: [PATCH 267/282] SwiftUTI: Remove the no more maintained pod. Embed code instead --- Podfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Podfile b/Podfile index f3dc507a94..5b21ad8610 100644 --- a/Podfile +++ b/Podfile @@ -61,8 +61,7 @@ abstract_target 'RiotPods' do pod 'GBDeviceInfo', '~> 6.3.0' pod 'Reusable', '~> 4.1' - pod 'SwiftUTI', :git => 'https://github.com/speramusinc/SwiftUTI.git', :branch => 'master' - + # Piwik for analytics pod 'MatomoTracker', '~> 7.2.0' From ee62f3b6f25c45a2df23f2a9179582f25745ff77 Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 14 Apr 2020 12:50:53 +0200 Subject: [PATCH 268/282] KeyVerificationCoordinatorBridgePresenterDelegate: Add keyVerificationCoordinatorBridgePresenterDelegateDidCancel --- Riot/AppDelegate.m | 10 ++++++++++ .../AuthenticationViewController.m | 8 +++++++- .../EncryptionInfo/EncryptionInfoView.m | 14 ++++++++++++-- .../Common/KeyVerificationCoordinator.swift | 2 +- ...erificationCoordinatorBridgePresenter.swift | 8 +++++++- .../KeyVerificationCoordinatorType.swift | 1 + .../User/UserVerificationCoordinator.swift | 8 ++++++++ .../Detail/RoomMemberDetailsViewController.m | 10 ++++++++++ .../RoomKeyRequestViewController.m | 18 ++++++++++++++---- .../UserDevices/UsersDevicesViewController.m | 18 ++++++++++++++---- 10 files changed, 84 insertions(+), 13 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index bb21728b08..881604cb01 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -4844,6 +4844,16 @@ - (BOOL)presentSelfVerificationForOtherDeviceId:(NSString*)deviceId inSession:(M } - (void)keyVerificationCoordinatorBridgePresenterDelegateDidComplete:(KeyVerificationCoordinatorBridgePresenter *)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId +{ + [self dismissKeyVerificationCoordinatorBridgePresenter]; +} + +- (void)keyVerificationCoordinatorBridgePresenterDelegateDidCancel:(KeyVerificationCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter +{ + [self dismissKeyVerificationCoordinatorBridgePresenter]; +} + +- (void)dismissKeyVerificationCoordinatorBridgePresenter { [keyVerificationCoordinatorBridgePresenter dismissWithAnimated:YES completion:^{ [self checkPendingIncomingKeyVerifications]; diff --git a/Riot/Modules/Authentication/AuthenticationViewController.m b/Riot/Modules/Authentication/AuthenticationViewController.m index 9147ff8af9..22d0108b41 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.m +++ b/Riot/Modules/Authentication/AuthenticationViewController.m @@ -1347,7 +1347,13 @@ - (void)showCustomHomeserver:(NSString*)homeserver andIdentityServer:(NSString*) #pragma mark - KeyVerificationCoordinatorBridgePresenterDelegate -- (void)keyVerificationCoordinatorBridgePresenterDelegateDidComplete:(KeyVerificationCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId { +- (void)keyVerificationCoordinatorBridgePresenterDelegateDidComplete:(KeyVerificationCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId +{ + [self dismiss]; +} + +- (void)keyVerificationCoordinatorBridgePresenterDelegateDidCancel:(KeyVerificationCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter +{ [self dismiss]; } diff --git a/Riot/Modules/EncryptionInfo/EncryptionInfoView.m b/Riot/Modules/EncryptionInfo/EncryptionInfoView.m index df568f8ce5..1043f3616d 100644 --- a/Riot/Modules/EncryptionInfo/EncryptionInfoView.m +++ b/Riot/Modules/EncryptionInfo/EncryptionInfoView.m @@ -71,11 +71,21 @@ - (void)onButtonPressed:(id)sender } } -- (void)keyVerificationCoordinatorBridgePresenterDelegateDidComplete:(KeyVerificationCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId { +- (void)keyVerificationCoordinatorBridgePresenterDelegateDidComplete:(KeyVerificationCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId +{ + [self dismissKeyVerificationCoordinatorBridgePresenter]; +} + +- (void)keyVerificationCoordinatorBridgePresenterDelegateDidCancel:(KeyVerificationCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter +{ + [self dismissKeyVerificationCoordinatorBridgePresenter]; +} +- (void)dismissKeyVerificationCoordinatorBridgePresenter +{ [keyVerificationCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil]; keyVerificationCoordinatorBridgePresenter = nil; - + // Eject like MXKEncryptionInfoView does [self removeFromSuperview]; } diff --git a/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinator.swift b/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinator.swift index 54dc6d94f0..65cebca08f 100644 --- a/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinator.swift +++ b/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinator.swift @@ -146,7 +146,7 @@ final class KeyVerificationCoordinator: KeyVerificationCoordinatorType { } private func didCancel() { - self.didComplete() + self.delegate?.keyVerificationCoordinatorDidCancel(self) } private func createCompleteSecurityCoordinator() -> KeyVerificationSelfVerifyWaitCoordinatorType { diff --git a/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinatorBridgePresenter.swift b/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinatorBridgePresenter.swift index 2786a7bcfa..e6bd0a697d 100644 --- a/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinatorBridgePresenter.swift +++ b/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinatorBridgePresenter.swift @@ -20,6 +20,7 @@ import Foundation @objc protocol KeyVerificationCoordinatorBridgePresenterDelegate { func keyVerificationCoordinatorBridgePresenterDelegateDidComplete(_ coordinatorBridgePresenter: KeyVerificationCoordinatorBridgePresenter, otherUserId: String, otherDeviceId: String) + func keyVerificationCoordinatorBridgePresenterDelegateDidCancel(_ coordinatorBridgePresenter: KeyVerificationCoordinatorBridgePresenter) } /// KeyVerificationCoordinatorBridgePresenter enables to start KeyVerificationCoordinator from a view controller. @@ -31,7 +32,7 @@ final class KeyVerificationCoordinatorBridgePresenter: NSObject { // MARK: Private - private let session: MXSession + let session: MXSession private var coordinator: KeyVerificationCoordinator? // MARK: Public @@ -132,7 +133,12 @@ final class KeyVerificationCoordinatorBridgePresenter: NSObject { // MARK: - KeyVerificationCoordinatorDelegate extension KeyVerificationCoordinatorBridgePresenter: KeyVerificationCoordinatorDelegate { + func keyVerificationCoordinatorDidComplete(_ coordinator: KeyVerificationCoordinatorType, otherUserId: String, otherDeviceId: String) { self.delegate?.keyVerificationCoordinatorBridgePresenterDelegateDidComplete(self, otherUserId: otherUserId, otherDeviceId: otherDeviceId) } + + func keyVerificationCoordinatorDidCancel(_ coordinator: KeyVerificationCoordinatorType) { + self.delegate?.keyVerificationCoordinatorBridgePresenterDelegateDidCancel(self) + } } diff --git a/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinatorType.swift b/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinatorType.swift index 5e84c151f2..901d6b11d4 100644 --- a/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinatorType.swift +++ b/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinatorType.swift @@ -20,6 +20,7 @@ import Foundation protocol KeyVerificationCoordinatorDelegate: class { func keyVerificationCoordinatorDidComplete(_ coordinator: KeyVerificationCoordinatorType, otherUserId: String, otherDeviceId: String) + func keyVerificationCoordinatorDidCancel(_ coordinator: KeyVerificationCoordinatorType) } /// `KeyVerificationCoordinatorType` is a protocol describing a Coordinator that handle key verification navigation flow. diff --git a/Riot/Modules/KeyVerification/User/UserVerificationCoordinator.swift b/Riot/Modules/KeyVerification/User/UserVerificationCoordinator.swift index 7e49fce01e..a3cc53577f 100644 --- a/Riot/Modules/KeyVerification/User/UserVerificationCoordinator.swift +++ b/Riot/Modules/KeyVerification/User/UserVerificationCoordinator.swift @@ -176,6 +176,14 @@ extension UserVerificationCoordinator: UserVerificationSessionStatusCoordinatorD extension UserVerificationCoordinator: KeyVerificationCoordinatorDelegate { func keyVerificationCoordinatorDidComplete(_ coordinator: KeyVerificationCoordinatorType, otherUserId: String, otherDeviceId: String) { + dismissPresenter(coordinator: coordinator) + } + + func keyVerificationCoordinatorDidCancel(_ coordinator: KeyVerificationCoordinatorType) { + dismissPresenter(coordinator: coordinator) + } + + func dismissPresenter(coordinator: KeyVerificationCoordinatorType) { self.presenter.toPresentable().dismiss(animated: true) { self.remove(childCoordinator: coordinator) } diff --git a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m index d323154fb1..2736c15be0 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m @@ -1359,6 +1359,16 @@ - (void)roomMemberTitleViewDidLayoutSubview:(RoomMemberTitleView*)titleView #pragma mark - KeyVerificationCoordinatorBridgePresenterDelegate - (void)keyVerificationCoordinatorBridgePresenterDelegateDidComplete:(KeyVerificationCoordinatorBridgePresenter *)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId +{ + [self dismissKeyVerificationCoordinatorBridgePresenter]; +} + +- (void)keyVerificationCoordinatorBridgePresenterDelegateDidCancel:(KeyVerificationCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter +{ + [self dismissKeyVerificationCoordinatorBridgePresenter]; +} + +- (void)dismissKeyVerificationCoordinatorBridgePresenter { [keyVerificationCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil]; keyVerificationCoordinatorBridgePresenter = nil; diff --git a/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.m b/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.m index 168662b4eb..12e04b1c63 100644 --- a/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.m +++ b/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.m @@ -146,20 +146,30 @@ - (void)showVerificationView #pragma mark - DeviceVerificationCoordinatorBridgePresenterDelegate - (void)keyVerificationCoordinatorBridgePresenterDelegateDidComplete:(KeyVerificationCoordinatorBridgePresenter *)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId +{ + [self dismissKeyVerificationCoordinatorBridgePresenter]; +} + +- (void)keyVerificationCoordinatorBridgePresenterDelegateDidCancel:(KeyVerificationCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter +{ + [self dismissKeyVerificationCoordinatorBridgePresenter]; +} + +- (void)dismissKeyVerificationCoordinatorBridgePresenter { [keyVerificationCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil]; keyVerificationCoordinatorBridgePresenter = nil; - + // Check device new status [self.mxSession.crypto downloadKeys:@[self.device.userId] forceDownload:NO success:^(MXUsersDevicesMap *usersDevicesInfoMap, NSDictionary *crossSigningKeysMap) { - + MXDeviceInfo *deviceInfo = [usersDevicesInfoMap objectForDevice:self.device.deviceId forUser:self.device.userId]; if (deviceInfo && deviceInfo.trustLevel.localVerificationStatus == MXDeviceVerified) { // Accept the received requests from this device // As the device is now verified, all other key requests will be automatically accepted. [self.mxSession.crypto acceptAllPendingKeyRequestsFromUser:self.device.userId andDevice:self.device.deviceId onComplete:^{ - + onComplete(); }]; } @@ -169,7 +179,7 @@ - (void)keyVerificationCoordinatorBridgePresenterDelegateDidComplete:(KeyVerific [self show]; } } failure:^(NSError *error) { - + // Should not happen (the device is in the crypto db) [self show]; }]; diff --git a/Riot/Modules/UserDevices/UsersDevicesViewController.m b/Riot/Modules/UserDevices/UsersDevicesViewController.m index 58a89c0d98..bac4e6be6c 100644 --- a/Riot/Modules/UserDevices/UsersDevicesViewController.m +++ b/Riot/Modules/UserDevices/UsersDevicesViewController.m @@ -246,17 +246,27 @@ - (void)reloadDataforUser:(NSString *)userId andDevice:(NSString *)deviceId - (void)keyVerificationCoordinatorBridgePresenterDelegateDidComplete:(KeyVerificationCoordinatorBridgePresenter *)coordinatorBridgePresenter otherUserId:(NSString * _Nonnull)otherUserId otherDeviceId:(NSString * _Nonnull)otherDeviceId { - [keyVerificationCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil]; - keyVerificationCoordinatorBridgePresenter = nil; - // Update our map [mxSession.crypto downloadKeys:@[otherUserId] forceDownload:NO success:^(MXUsersDevicesMap *usersDevicesInfoMap, NSDictionary *crossSigningKeysMap) { [self reloadDataforUser:otherUserId andDevice:otherDeviceId]; - + } failure:^(NSError *error) { // Should not happen (the device is in the crypto db) }]; + + [self dismissKeyVerificationCoordinatorBridgePresenter]; +} + +- (void)keyVerificationCoordinatorBridgePresenterDelegateDidCancel:(KeyVerificationCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter +{ + [self dismissKeyVerificationCoordinatorBridgePresenter]; +} + +- (void)dismissKeyVerificationCoordinatorBridgePresenter +{ + [keyVerificationCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil]; + keyVerificationCoordinatorBridgePresenter = nil; } From de13946fcd18d2ed0be27edb468510047c64853a Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 14 Apr 2020 12:55:25 +0200 Subject: [PATCH 269/282] New Sign-in: Do not send key requests before completing the "security" --- .../Modules/Authentication/AuthenticationViewController.m | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Riot/Modules/Authentication/AuthenticationViewController.m b/Riot/Modules/Authentication/AuthenticationViewController.m index 22d0108b41..1155c89a53 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.m +++ b/Riot/Modules/Authentication/AuthenticationViewController.m @@ -1185,6 +1185,11 @@ - (void)sessionStateDidChangeNotification:(NSNotification*)notification if (session.crypto.crossSigning) { + // Do not make key share requests while the "Complete security" is not complete. + // If the device is self-verified, the SDK will restore the existing key backup. + // Then, it will re-enable outgoing key share requests + [session.crypto setOutgoingKeyRequestsEnabled:NO onComplete:nil]; + [session.crypto.crossSigning refreshStateWithSuccess:^(BOOL stateUpdated) { if (session.crypto.crossSigning.state == MXCrossSigningStateCrossSigningExists) @@ -1199,6 +1204,7 @@ - (void)sessionStateDidChangeNotification:(NSNotification*)notification } else { + [session.crypto setOutgoingKeyRequestsEnabled:YES onComplete:nil]; [self dismiss]; } @@ -1354,6 +1360,8 @@ - (void)keyVerificationCoordinatorBridgePresenterDelegateDidComplete:(KeyVerific - (void)keyVerificationCoordinatorBridgePresenterDelegateDidCancel:(KeyVerificationCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter { + // Set outgoing key requests back + [coordinatorBridgePresenter.session.crypto setOutgoingKeyRequestsEnabled:YES onComplete:nil]; [self dismiss]; } From 737c5cff63736f24b8d356076509733242d96bb0 Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Wed, 15 Apr 2020 18:45:46 +0300 Subject: [PATCH 270/282] DeactivateAccountViewController theme, fixes #2993 Signed-off-by: ismailgulek --- .../DeactivateAccountViewController.m | 11 ++++++++--- .../DeactivateAccountViewController.storyboard | 8 ++++---- Riot/Modules/Settings/SettingsViewController.m | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.m b/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.m index 9751461303..b5917fc845 100644 --- a/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.m +++ b/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.m @@ -86,15 +86,14 @@ - (void)viewDidLoad self.title = NSLocalizedStringFromTable(@"deactivate_account_title", @"Vector", nil); self.errorPresentation = [[MXKErrorAlertPresentation alloc] init]; - [self setupStringAttributes]; - [self setupViews]; - [self userInterfaceThemeDidChange]; [self registerThemeNotification]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; + + [self userInterfaceThemeDidChange]; // Screen tracking [[Analytics sharedInstance] trackScreen:@"DeactivateAccount"]; @@ -126,6 +125,12 @@ - (void)userInterfaceThemeDidChange [ThemeService.shared.theme applyStyleOnNavigationBar:self.navigationController.navigationBar]; self.activityIndicator.backgroundColor = ThemeService.shared.theme.overlayBackgroundColor; + + self.view.backgroundColor = ThemeService.shared.theme.backgroundColor; + + [self setupStringAttributes]; + + [self setupViews]; } - (void)setupStringAttributes diff --git a/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.storyboard b/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.storyboard index 687d1ef050..13f41d437f 100644 --- a/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.storyboard +++ b/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.storyboard @@ -1,12 +1,11 @@ - + - - + @@ -57,7 +56,7 @@ Message visibility in Matrix is similar to email. Our forgetting your messages m + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewController.swift b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewController.swift new file mode 100644 index 0000000000..ec31339462 --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewController.swift @@ -0,0 +1,241 @@ +// File created from ScreenTemplate +// $ createScreen.sh KeyVerification/Common/ScanConfirmation KeyVerificationScanConfirmation +/* + Copyright 2020 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 KeyVerificationScanConfirmationViewController: UIViewController { + + // MARK: - Constants + + private enum Constants { + static let buttonBackgroundColorAlpha: CGFloat = 0.2 + static let buttonCornerRadius: CGFloat = 6.0 + } + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var scrollView: UIScrollView! + + @IBOutlet private weak var titleLabel: UILabel! + + @IBOutlet private weak var waitingLabel: UILabel! + + @IBOutlet private weak var scannedContentView: UIView! + @IBOutlet private weak var scannedInformationLabel: UILabel! + @IBOutlet private weak var rejectButton: UIButton! + @IBOutlet private weak var confirmButton: UIButton! + + // MARK: Private + + private var viewModel: KeyVerificationScanConfirmationViewModelType! + private var theme: Theme! + private var errorPresenter: MXKErrorPresentation! + private var activityPresenter: ActivityIndicatorPresenter! + + // MARK: - Setup + + class func instantiate(with viewModel: KeyVerificationScanConfirmationViewModelType) -> KeyVerificationScanConfirmationViewController { + let viewController = StoryboardScene.KeyVerificationScanConfirmationViewController.initialScene.instantiate() + viewController.viewModel = viewModel + viewController.theme = ThemeService.shared().theme + return viewController + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + self.setupViews() + self.activityPresenter = ActivityIndicatorPresenter() + self.errorPresenter = MXKErrorAlertPresentation() + + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + + self.viewModel.viewDelegate = self + + self.viewModel.process(viewAction: .loadData) + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + if self.scannedContentView.isHidden == false { + self.confirmButton.layer.cornerRadius = Constants.buttonCornerRadius + self.rejectButton.layer.cornerRadius = Constants.buttonCornerRadius + } + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + // Hide back button + self.navigationItem.setHidesBackButton(true, animated: animated) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.theme.statusBarStyle + } + + // MARK: - Private + + private func update(theme: Theme) { + self.theme = theme + + self.view.backgroundColor = theme.headerBackgroundColor + + if let navigationBar = self.navigationController?.navigationBar { + theme.applyStyle(onNavigationBar: navigationBar) + } + + self.titleLabel.textColor = theme.textPrimaryColor + self.waitingLabel.textColor = theme.textSecondaryColor + self.scannedInformationLabel.textColor = theme.textPrimaryColor + self.confirmButton.vc_setBackgroundColor(theme.tintColor.withAlphaComponent(Constants.buttonBackgroundColorAlpha), for: .normal) + self.rejectButton.vc_setBackgroundColor(theme.noticeColor.withAlphaComponent(Constants.buttonBackgroundColorAlpha), for: .normal) + } + + 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() { + let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in + self?.cancelButtonAction() + } + + self.navigationItem.rightBarButtonItem = cancelBarButtonItem + + self.title = VectorL10n.keyVerificationVerifyQrCodeTitle + + self.confirmButton.layer.masksToBounds = true + self.rejectButton.layer.masksToBounds = true + } + + private func render(viewState: KeyVerificationScanConfirmationViewState) { + switch viewState { + case .loading: + self.renderLoading() + case .loaded(let viewData): + self.renderLoaded(viewData: viewData) + case .error(let error): + self.render(error: error) + case .cancelled(let reason): + self.renderCancelled(reason: reason) + case .cancelledByMe(let reason): + self.renderCancelledByMe(reason: reason) + } + } + + private func renderLoading() { + self.activityPresenter.presentActivityIndicator(on: self.view, animated: true) + } + + private func renderLoaded(viewData: KeyVerificationScanConfirmationViewData) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + + self.waitingLabel.isHidden = !viewData.isScanning + self.scannedContentView.isHidden = viewData.isScanning + + var title: String + var waitingInfo: String? + var scannedInfo: String? + + if viewData.isScanning { + title = VectorL10n.keyVerificationScanConfirmationScanningTitle + + switch viewData.verificationKind { + case .device: + waitingInfo = VectorL10n.keyVerificationScanConfirmationScanningDeviceWaitingOther + case .user: + waitingInfo = VectorL10n.keyVerificationScanConfirmationScanningUserWaitingOther(viewData.otherDisplayName) + } + } else { + title = VectorL10n.keyVerificationScanConfirmationScannedTitle + + switch viewData.verificationKind { + case .device: + scannedInfo = VectorL10n.keyVerificationScanConfirmationScannedDeviceInformation + case .user: + scannedInfo = VectorL10n.keyVerificationScanConfirmationScannedUserInformation(viewData.otherDisplayName) + } + } + + self.titleLabel.text = title + self.waitingLabel.text = waitingInfo + self.scannedInformationLabel.text = scannedInfo + } + + private func renderCancelled(reason: MXTransactionCancelCode) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + + self.errorPresenter.presentError(from: self, title: "", message: VectorL10n.deviceVerificationCancelled, animated: true) { + self.viewModel.process(viewAction: .cancel) + } + } + + private func renderCancelledByMe(reason: MXTransactionCancelCode) { + if reason.value != MXTransactionCancelCode.user().value { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + + self.errorPresenter.presentError(from: self, title: "", message: VectorL10n.deviceVerificationCancelledByMe(reason.humanReadable), animated: true) { + self.viewModel.process(viewAction: .cancel) + } + } else { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + } + } + + private func render(error: Error) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil) + } + + + // MARK: - Actions + + @IBAction private func rejectButtonAction(_ sender: Any) { + self.viewModel.process(viewAction: .acknowledgeOtherScannedMyCode(false)) + } + + @IBAction private func confirmButtonAction(_ sender: Any) { + self.viewModel.process(viewAction: .acknowledgeOtherScannedMyCode(true)) + } + + private func cancelButtonAction() { + self.viewModel.process(viewAction: .cancel) + } +} + + +// MARK: - KeyVerificationScanConfirmationViewModelViewDelegate +extension KeyVerificationScanConfirmationViewController: KeyVerificationScanConfirmationViewModelViewDelegate { + + func keyVerificationScanConfirmationViewModel(_ viewModel: KeyVerificationScanConfirmationViewModelType, didUpdateViewState viewSate: KeyVerificationScanConfirmationViewState) { + self.render(viewState: viewSate) + } +} diff --git a/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewModel.swift b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewModel.swift new file mode 100644 index 0000000000..7407a0e2ff --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewModel.swift @@ -0,0 +1,137 @@ +// File created from ScreenTemplate +// $ createScreen.sh KeyVerification/Common/ScanConfirmation KeyVerificationScanConfirmation +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +final class KeyVerificationScanConfirmationViewModel: KeyVerificationScanConfirmationViewModelType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let transaction: MXQRCodeTransaction + private let codeScanning: KeyVerificationScanning + private let verificationKind: KeyVerificationKind + + private var isScanning: Bool { + if case .scannedOtherQRCode = self.codeScanning { + return true + } + return false + } + + // MARK: Public + + weak var viewDelegate: KeyVerificationScanConfirmationViewModelViewDelegate? + weak var coordinatorDelegate: KeyVerificationScanConfirmationViewModelCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, + transaction: MXQRCodeTransaction, + codeScanning: KeyVerificationScanning, + verificationKind: KeyVerificationKind) { + self.session = session + self.transaction = transaction + self.codeScanning = codeScanning + self.verificationKind = verificationKind + } + + deinit { + } + + // MARK: - Public + + func process(viewAction: KeyVerificationScanConfirmationViewAction) { + switch viewAction { + case .loadData: + self.loadData() + case .acknowledgeOtherScannedMyCode(let otherHasScannedMyCode): + self.transaction.otherUserScannedMyQrCode(otherHasScannedMyCode) + if otherHasScannedMyCode == false { self.coordinatorDelegate?.keyVerificationScanConfirmationViewModelDidCancel(self) + } else { + self.update(viewState: .loading) + } + case .cancel: + self.cancel() + } + } + + // MARK: - Private + + private func loadData() { + let otherUserId = self.transaction.otherUserId + let otherUser = self.session.user(withUserId: otherUserId) + let otherDisplayName = otherUser?.displayname ?? otherUserId + + let viewData = KeyVerificationScanConfirmationViewData(isScanning: self.isScanning, verificationKind: self.verificationKind, otherDisplayName: otherDisplayName) + self.update(viewState: .loaded(viewData)) + + self.registerTransactionDidStateChangeNotification() + + if case .scannedOtherQRCode(let qrCodeData) = self.codeScanning { + self.transaction.userHasScannedOtherQrCodeData(qrCodeData) + } + } + + private func update(viewState: KeyVerificationScanConfirmationViewState) { + self.viewDelegate?.keyVerificationScanConfirmationViewModel(self, didUpdateViewState: viewState) + } + + private func cancel() { + self.transaction.cancel(with: MXTransactionCancelCode.user()) + self.coordinatorDelegate?.keyVerificationScanConfirmationViewModelDidCancel(self) + } + + // MARK: - MXKeyVerificationTransactionDidChange + + private func registerTransactionDidStateChangeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(transactionDidStateChange(notification:)), name: .MXKeyVerificationTransactionDidChange, object: self.transaction) + } + + private func unregisterTransactionDidStateChangeNotification() { + NotificationCenter.default.removeObserver(self, name: .MXKeyVerificationTransactionDidChange, object: nil) + } + + @objc private func transactionDidStateChange(notification: Notification) { + guard let transaction = notification.object as? MXQRCodeTransaction else { + return + } + + switch transaction.state { + case .verified: + self.unregisterTransactionDidStateChangeNotification() + self.coordinatorDelegate?.keyVerificationScanConfirmationViewModelDidComplete(self) + case .cancelled: + guard let reason = transaction.reasonCancelCode else { + return + } + self.unregisterTransactionDidStateChangeNotification() + self.update(viewState: .cancelled(reason)) + case .cancelledByMe: + guard let reason = transaction.reasonCancelCode else { + return + } + self.unregisterTransactionDidStateChangeNotification() + self.update(viewState: .cancelledByMe(reason)) + default: + break + } + } +} diff --git a/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewModelType.swift b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewModelType.swift new file mode 100644 index 0000000000..03d843560d --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewModelType.swift @@ -0,0 +1,37 @@ +// File created from ScreenTemplate +// $ createScreen.sh KeyVerification/Common/ScanConfirmation KeyVerificationScanConfirmation +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +protocol KeyVerificationScanConfirmationViewModelViewDelegate: class { + func keyVerificationScanConfirmationViewModel(_ viewModel: KeyVerificationScanConfirmationViewModelType, didUpdateViewState viewSate: KeyVerificationScanConfirmationViewState) +} + +protocol KeyVerificationScanConfirmationViewModelCoordinatorDelegate: class { + func keyVerificationScanConfirmationViewModelDidComplete(_ viewModel: KeyVerificationScanConfirmationViewModelType) + func keyVerificationScanConfirmationViewModelDidCancel(_ viewModel: KeyVerificationScanConfirmationViewModelType) +} + +/// Protocol describing the view model used by `KeyVerificationScanConfirmationViewController` +protocol KeyVerificationScanConfirmationViewModelType { + + var viewDelegate: KeyVerificationScanConfirmationViewModelViewDelegate? { get set } + var coordinatorDelegate: KeyVerificationScanConfirmationViewModelCoordinatorDelegate? { get set } + + func process(viewAction: KeyVerificationScanConfirmationViewAction) +} diff --git a/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewState.swift b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewState.swift new file mode 100644 index 0000000000..c91b35a9f6 --- /dev/null +++ b/Riot/Modules/KeyVerification/Common/ScanConfirmation/KeyVerificationScanConfirmationViewState.swift @@ -0,0 +1,34 @@ +// File created from ScreenTemplate +// $ createScreen.sh KeyVerification/Common/ScanConfirmation KeyVerificationScanConfirmation +/* + Copyright 2020 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +struct KeyVerificationScanConfirmationViewData { + let isScanning: Bool + let verificationKind: KeyVerificationKind + let otherDisplayName: String +} + +/// KeyVerificationScanConfirmationViewController view state +enum KeyVerificationScanConfirmationViewState { + case loading + case loaded(_ viewData: KeyVerificationScanConfirmationViewData) + case cancelled(MXTransactionCancelCode) + case cancelledByMe(MXTransactionCancelCode) + case error(Error) +} From 957fe93bd829ad9005061bd1b9e88eb52c7e5b9c Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 16 Apr 2020 18:45:46 +0200 Subject: [PATCH 275/282] KeyVerification: Update scanning screen to handle new QR code confirmation state. --- .../KeyVerificationVerifyByScanningCoordinator.swift | 8 ++++++-- .../KeyVerificationVerifyByScanningCoordinatorType.swift | 3 ++- .../KeyVerificationVerifyByScanningViewModel.swift | 8 +++++--- .../KeyVerificationVerifyByScanningViewModelType.swift | 6 +++++- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningCoordinator.swift b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningCoordinator.swift index 344c1be444..1002d8cd31 100644 --- a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningCoordinator.swift +++ b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningCoordinator.swift @@ -76,7 +76,11 @@ extension KeyVerificationVerifyByScanningCoordinator: KeyVerificationVerifyBySca self.delegate?.keyVerificationVerifyByScanningCoordinator(self, didCompleteWithSASTransaction: transaction) } - func keyVerificationVerifyByScanningViewModelDidCompleteQRCodeVerification(_ viewModel: KeyVerificationVerifyByScanningViewModelType) { - self.delegate?.keyVerificationVerifyByScanningCoordinatorDidCompleteQRCodeVerification(self) + func keyVerificationVerifyByScanningViewModel(_ viewModel: KeyVerificationVerifyByScanningViewModelType, didScanOtherQRCodeData qrCodeData: MXQRCodeData, withTransaction transaction: MXQRCodeTransaction) { + self.delegate?.keyVerificationVerifyByScanningCoordinator(self, didScanOtherQRCodeData: qrCodeData, withTransaction: transaction) + } + + func keyVerificationVerifyByScanningViewModel(_ viewModel: KeyVerificationVerifyByScanningViewModelType, qrCodeDidScannedByOtherWithTransaction transaction: MXQRCodeTransaction) { + self.delegate?.keyVerificationVerifyByScanningCoordinator(self, qrCodeDidScannedByOtherWithTransaction: transaction) } } diff --git a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningCoordinatorType.swift b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningCoordinatorType.swift index 50cb3a3807..faa3ec9ee6 100644 --- a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningCoordinatorType.swift +++ b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningCoordinatorType.swift @@ -21,7 +21,8 @@ import Foundation protocol KeyVerificationVerifyByScanningCoordinatorDelegate: class { func keyVerificationVerifyByScanningCoordinatorDidCancel(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType) func keyVerificationVerifyByScanningCoordinatorCannotScan(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType) - func keyVerificationVerifyByScanningCoordinatorDidCompleteQRCodeVerification(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType) + func keyVerificationVerifyByScanningCoordinator(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType, didScanOtherQRCodeData qrCodeData: MXQRCodeData, withTransaction transaction: MXQRCodeTransaction) + func keyVerificationVerifyByScanningCoordinator(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType, qrCodeDidScannedByOtherWithTransaction transaction: MXQRCodeTransaction) func keyVerificationVerifyByScanningCoordinator(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType, didCompleteWithSASTransaction transaction: MXSASTransaction) } diff --git a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift index 087c0fe0d3..8462e051bb 100644 --- a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift +++ b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModel.swift @@ -145,16 +145,17 @@ final class KeyVerificationVerifyByScanningViewModel: KeyVerificationVerifyBySca return } - qrCodeTransaction.userHasScannedOtherQrCodeData(scannedQRCodeData) self.update(viewState: .loading) + self.coordinatorDelegate?.keyVerificationVerifyByScanningViewModel(self, didScanOtherQRCodeData: scannedQRCodeData, withTransaction: qrCodeTransaction) } private func acknowledgeOtherScannedMyCode(_ acknowledgeOtherScannedMyCode: Bool) { guard let qrCodeTransaction = self.qrCodeTransaction else { return } + self.update(viewState: .loading) - qrCodeTransaction.otherUserScannedMyQrCode(acknowledgeOtherScannedMyCode) + self.coordinatorDelegate?.keyVerificationVerifyByScanningViewModel(self, qrCodeDidScannedByOtherWithTransaction: qrCodeTransaction) } private func removePendingQRCodeTransaction() { @@ -245,8 +246,9 @@ final class KeyVerificationVerifyByScanningViewModel: KeyVerificationVerifyBySca private func qrCodeTransactionDidStateChange(_ transaction: MXQRCodeTransaction) { switch transaction.state { case .verified: + // Should not happen self.unregisterTransactionDidStateChangeNotification() - self.coordinatorDelegate?.keyVerificationVerifyByScanningViewModelDidCompleteQRCodeVerification(self) + self.coordinatorDelegate?.keyVerificationVerifyByScanningViewModelDidCancel(self) case .qrScannedByOther: self.update(viewState: .otherUserScannedMyCode) case .cancelled: diff --git a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModelType.swift b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModelType.swift index 561de52770..4be0568d5a 100644 --- a/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModelType.swift +++ b/Riot/Modules/KeyVerification/Common/Verify/Scanning/KeyVerificationVerifyByScanningViewModelType.swift @@ -24,7 +24,11 @@ protocol KeyVerificationVerifyByScanningViewModelViewDelegate: class { protocol KeyVerificationVerifyByScanningViewModelCoordinatorDelegate: class { func keyVerificationVerifyByScanningViewModelDidCancel(_ viewModel: KeyVerificationVerifyByScanningViewModelType) - func keyVerificationVerifyByScanningViewModelDidCompleteQRCodeVerification(_ viewModel: KeyVerificationVerifyByScanningViewModelType) + + func keyVerificationVerifyByScanningViewModel(_ viewModel: KeyVerificationVerifyByScanningViewModelType, didScanOtherQRCodeData qrCodeData: MXQRCodeData, withTransaction transaction: MXQRCodeTransaction) + + func keyVerificationVerifyByScanningViewModel(_ viewModel: KeyVerificationVerifyByScanningViewModelType, qrCodeDidScannedByOtherWithTransaction transaction: MXQRCodeTransaction) + func keyVerificationVerifyByScanningViewModel(_ viewModel: KeyVerificationVerifyByScanningViewModelType, didStartSASVerificationWithTransaction transaction: MXSASTransaction) } From 9709c678868b3c53c2fb6bc395a6e18af4e1aab3 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 16 Apr 2020 18:46:18 +0200 Subject: [PATCH 276/282] KeyVerificationCoordinator: Handle QR code confirmation screen. --- .../Common/KeyVerificationCoordinator.swift | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinator.swift b/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinator.swift index 65cebca08f..aaf890f89d 100644 --- a/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinator.swift +++ b/Riot/Modules/KeyVerification/Common/KeyVerificationCoordinator.swift @@ -102,7 +102,7 @@ final class KeyVerificationCoordinator: KeyVerificationCoordinatorType { func start() { let rootCoordinator: Coordinator & Presentable - + switch self.verificationFlow { case .verifyUser(let roomMember): rootCoordinator = self.createUserVerificationStartCoordinator(with: roomMember) @@ -119,11 +119,11 @@ final class KeyVerificationCoordinator: KeyVerificationCoordinatorType { case .completeSecurity: rootCoordinator = self.createCompleteSecurityCoordinator() } - + rootCoordinator.start() - + self.add(childCoordinator: rootCoordinator) - + if self.navigationRouter.modules.isEmpty == false { self.navigationRouter.push(rootCoordinator, animated: true, popCompletion: { [weak self] in self?.remove(childCoordinator: rootCoordinator) @@ -232,6 +232,17 @@ final class KeyVerificationCoordinator: KeyVerificationCoordinatorType { self?.remove(childCoordinator: coordinator) } } + + private func showScanConfirmation(for transaction: MXQRCodeTransaction, codeScanning: KeyVerificationScanning, animated: Bool) { + let coordinator = KeyVerificationScanConfirmationCoordinator(session: self.session, transaction: transaction, codeScanning: codeScanning, verificationKind: self.verificationKind) + coordinator.delegate = self + coordinator.start() + + self.add(childCoordinator: coordinator) + self.navigationRouter.push(coordinator, animated: animated) { [weak self] in + self?.remove(childCoordinator: coordinator) + } + } private func showVerified(animated: Bool) { let viewController = KeyVerificationVerifiedViewController.instantiate(with: self.verificationKind) @@ -350,10 +361,14 @@ extension KeyVerificationCoordinator: KeyVerificationVerifyByScanningCoordinator self.showVerified(animated: true) } - func keyVerificationVerifyByScanningCoordinatorDidCompleteQRCodeVerification(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType) { - self.showVerified(animated: true) + func keyVerificationVerifyByScanningCoordinator(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType, didScanOtherQRCodeData qrCodeData: MXQRCodeData, withTransaction transaction: MXQRCodeTransaction) { + self.showScanConfirmation(for: transaction, codeScanning: .scannedOtherQRCode(qrCodeData), animated: true) } + func keyVerificationVerifyByScanningCoordinator(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType, qrCodeDidScannedByOtherWithTransaction transaction: MXQRCodeTransaction) { + self.showScanConfirmation(for: transaction, codeScanning: .myQRCodeScanned, animated: true) + } + func keyVerificationVerifyByScanningCoordinator(_ coordinator: KeyVerificationVerifyByScanningCoordinatorType, didCompleteWithSASTransaction transaction: MXSASTransaction) { self.showVerifyBySAS(transaction: transaction, animated: true) } @@ -381,3 +396,15 @@ extension KeyVerificationCoordinator: KeyVerificationSelfVerifyWaitCoordinatorDe self.didCancel() } } + +// MARK: - KeyVerificationScanConfirmationCoordinatorDelegate +extension KeyVerificationCoordinator: KeyVerificationScanConfirmationCoordinatorDelegate { + + func keyVerificationScanConfirmationCoordinatorDidComplete(_ coordinator: KeyVerificationScanConfirmationCoordinatorType) { + self.showVerified(animated: true) + } + + func keyVerificationScanConfirmationCoordinatorDidCancel(_ coordinator: KeyVerificationScanConfirmationCoordinatorType) { + self.didCancel() + } +} From ece92da07208617389b5c13554d6cfc0848321fb Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Fri, 17 Apr 2020 13:39:02 +0300 Subject: [PATCH 277/282] Import KTCenterFlowLayout pod Signed-off-by: ismailgulek --- Podfile | 1 + Podfile.lock | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Podfile b/Podfile index 5b21ad8610..87739abb74 100644 --- a/Podfile +++ b/Podfile @@ -77,6 +77,7 @@ abstract_target 'RiotPods' do target "Riot" do import_MatrixKit pod 'DGCollectionViewLeftAlignFlowLayout', '~> 1.0.4' + pod 'KTCenterFlowLayout', '~> 1.3.1' pod 'ZXingObjC', '~> 3.6.5' target 'RiotTests' do diff --git a/Podfile.lock b/Podfile.lock index f3e36eae99..074346fe16 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -44,6 +44,7 @@ PODS: - GZIP (1.2.3) - HPGrowingTextView (1.1) - JitsiMeetSDK (2.3.1) + - KTCenterFlowLayout (1.3.1) - libbase58 (0.1.4) - libPhoneNumber-iOS (0.9.15) - MatomoTracker (7.2.0): @@ -110,6 +111,7 @@ DEPENDENCIES: - cmark - DGCollectionViewLeftAlignFlowLayout (~> 1.0.4) - GBDeviceInfo (~> 6.3.0) + - KTCenterFlowLayout (~> 1.3.1) - MatomoTracker (~> 7.2.0) - MatrixKit (= 0.11.4) - MatrixKit/AppExtension (= 0.11.4) @@ -134,6 +136,7 @@ SPEC REPOS: - GZIP - HPGrowingTextView - JitsiMeetSDK + - KTCenterFlowLayout - libbase58 - libPhoneNumber-iOS - MatomoTracker @@ -167,6 +170,7 @@ SPEC CHECKSUMS: GZIP: af5c90ef903776a7e9afe6ebebd794a84a2929d4 HPGrowingTextView: 88a716d97fb853bcb08a4a08e4727da17efc9b19 JitsiMeetSDK: 69e4978fbab21f9a535d1bec3b8d43721a4c72b2 + KTCenterFlowLayout: 6e02b50ab2bd865025ae82fe266ed13b6d9eaf97 libbase58: 7c040313537b8c44b6e2d15586af8e21f7354efd libPhoneNumber-iOS: 0a32a9525cf8744fe02c5206eb30d571e38f7d75 MatomoTracker: 6f89e2561083685a360e223fb663e9ccd57c1d1a @@ -181,6 +185,6 @@ SPEC CHECKSUMS: zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: c7b37d248a316ae786328d88148e3155fea6bee3 +PODFILE CHECKSUM: 423280392fb3008372e2b92282dd0c08e4039c6f COCOAPODS: 1.8.4 From a94af7cb93393080da8dbeb14effd263e7d92a11 Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Fri, 17 Apr 2020 13:41:09 +0300 Subject: [PATCH 278/282] Use KTCenterFlowLayout for collection view layout, fixes #3088 Signed-off-by: ismailgulek --- .../SAS/KeyVerificationVerifyBySASViewController.storyboard | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewController.storyboard b/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewController.storyboard index 611ec4f217..89ca47a432 100644 --- a/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewController.storyboard +++ b/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewController.storyboard @@ -91,7 +91,7 @@ - + From d3b00e6c6f0cb229995add340d5e9a07d7bd58c1 Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Fri, 17 Apr 2020 13:43:46 +0300 Subject: [PATCH 279/282] Update CHANGES.rst Signed-off-by: ismailgulek --- CHANGES.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.rst b/CHANGES.rst index c3e9a67824..26359b2083 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -15,6 +15,7 @@ Improvements: * Cross-signing: Complete security at the end of sign in process( #3003). * Make decoration uniform (#2972). * DeactivateAccountViewController: Respect active theme (PR #3107). + * Verification by emojis: Center emojis in screen horizontally (PR #3119). Bug fix: * Key backup banner is not hidden correctly (#2899). From dfa74d4d5e7f6d99aecda776583c634d40fd411a Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Fri, 17 Apr 2020 14:39:52 +0300 Subject: [PATCH 280/282] Set collectionView width to constant, to show only 4 items in a row Signed-off-by: ismailgulek --- .../KeyVerificationVerifyBySASViewController.storyboard | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewController.storyboard b/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewController.storyboard index 89ca47a432..afd2968eb6 100644 --- a/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewController.storyboard +++ b/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewController.storyboard @@ -86,10 +86,11 @@ - + + @@ -141,7 +142,6 @@ - @@ -153,11 +153,11 @@ - - + + From df890f54ac2213a8e367b0895b620d3a9d4045ec Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Fri, 17 Apr 2020 17:02:09 +0300 Subject: [PATCH 281/282] Update third_party_licences.hmtl Signed-off-by: ismailgulek --- Riot/Assets/third_party_licenses.html | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Riot/Assets/third_party_licenses.html b/Riot/Assets/third_party_licenses.html index 3ab25b73b1..37848b5957 100644 --- a/Riot/Assets/third_party_licenses.html +++ b/Riot/Assets/third_party_licenses.html @@ -1403,6 +1403,28 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

    +
  • + KTCenterFlowLayout (https://github.com/keighl/KTCenterFlowLayout) +

    KTCenterFlowLayout is a subclass of UICollectionViewFlowLayout which Aligns cells to the center of a collection view. +

    It is released under the MIT license. +

    Copyright (c) keighl (http://github.com/keighl) +

    Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: +

    The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. +

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +

    +
  • From 654ca8de5eb313618041c2bc9b729b33eabc6ec6 Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 17 Apr 2020 19:04:21 +0200 Subject: [PATCH 282/282] version++ --- CHANGES.rst | 8 +++++--- Podfile | 2 +- Podfile.lock | 52 ++++++++++++++++++---------------------------------- 3 files changed, 24 insertions(+), 38 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 26359b2083..87e646278e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,9 +1,11 @@ -Changes in 0.11.0 (2020-xx-xx) +Changes in 0.11.0 (2020-04-17) =============================================== Improvements: - * E2E: Do not warn anymore for unknown devices - * ON/OFF Cross-signing development in a Lab setting (#2855). + * Upgrade MatrixKit version ([v0.12.0](https://github.com/matrix-org/matrix-ios-kit/releases/tag/v0.12.0)). + * Crypto: Enable E2EE by default for DM + * Crypto: Cross-signing support + * Crypto: Do not warn anymore for unknown devices. Trust on First Use. * RoomVC: Update encryption decoration with shields (#2934, #2930, #2906). * Settings: Remove "End-to-End Encryption" from the LABS section (#2941). * Room decoration: Use shields instead of padlocks (#2906). diff --git a/Podfile b/Podfile index 87739abb74..68c5cf2069 100644 --- a/Podfile +++ b/Podfile @@ -7,7 +7,7 @@ use_frameworks! # Different flavours of pods to MatrixKit # The current MatrixKit pod version -$matrixKitVersion = '0.11.4' +$matrixKitVersion = '0.12.0' # The develop branch version #$matrixKitVersion = 'develop' diff --git a/Podfile.lock b/Podfile.lock index 074346fe16..5ec7bb55f3 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -50,41 +50,38 @@ PODS: - MatomoTracker (7.2.0): - MatomoTracker/Core (= 7.2.0) - MatomoTracker/Core (7.2.0) - - MatrixKit (0.11.4): + - MatrixKit (0.12.0): - cmark (~> 0.24.1) - DTCoreText (~> 1.6.21) - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixKit/Core (= 0.11.4) - - MatrixSDK (= 0.15.2) - - SwiftUTI (~> 1.0.6) - - MatrixKit/AppExtension (0.11.4): + - MatrixKit/Core (= 0.12.0) + - MatrixSDK (= 0.16.0) + - MatrixKit/AppExtension (0.12.0): - cmark (~> 0.24.1) - DTCoreText (~> 1.6.21) - DTCoreText/Extension - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.15.2) - - SwiftUTI (~> 1.0.6) - - MatrixKit/Core (0.11.4): + - MatrixSDK (= 0.16.0) + - MatrixKit/Core (0.12.0): - cmark (~> 0.24.1) - DTCoreText (~> 1.6.21) - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.15.2) - - SwiftUTI (~> 1.0.6) - - MatrixSDK (0.15.2): - - MatrixSDK/Core (= 0.15.2) - - MatrixSDK/Core (0.15.2): + - MatrixSDK (= 0.16.0) + - MatrixSDK (0.16.0): + - MatrixSDK/Core (= 0.16.0) + - MatrixSDK/Core (0.16.0): - AFNetworking (~> 3.2.0) - GZIP (~> 1.2.2) - libbase58 (~> 0.1.4) - OLMKit (~> 3.1.0) - Realm (~> 3.17.3) - - MatrixSDK/JingleCallStack (0.15.2): + - MatrixSDK/JingleCallStack (0.16.0): - JitsiMeetSDK (~> 2.3.1) - MatrixSDK/Core - - MatrixSDK/SwiftSupport (0.15.2): + - MatrixSDK/SwiftSupport (0.16.0): - MatrixSDK/Core - OLMKit (3.1.0): - OLMKit/olmc (= 3.1.0) @@ -101,7 +98,6 @@ PODS: - Reusable/View (4.1.0) - SwiftGen (6.1.0) - SwiftLint (0.36.0) - - SwiftUTI (1.0.7) - zxcvbn-ios (1.0.4) - ZXingObjC (3.6.5): - ZXingObjC/All (= 3.6.5) @@ -113,15 +109,14 @@ DEPENDENCIES: - GBDeviceInfo (~> 6.3.0) - KTCenterFlowLayout (~> 1.3.1) - MatomoTracker (~> 7.2.0) - - MatrixKit (= 0.11.4) - - MatrixKit/AppExtension (= 0.11.4) + - MatrixKit (= 0.12.0) + - MatrixKit/AppExtension (= 0.12.0) - MatrixSDK/JingleCallStack - MatrixSDK/SwiftSupport - OLMKit - Reusable (~> 4.1) - SwiftGen (~> 6.1) - SwiftLint (~> 0.36.0) - - SwiftUTI (from `https://github.com/speramusinc/SwiftUTI.git`, branch `master`) - zxcvbn-ios - ZXingObjC (~> 3.6.5) @@ -150,16 +145,6 @@ SPEC REPOS: - zxcvbn-ios - ZXingObjC -EXTERNAL SOURCES: - SwiftUTI: - :branch: master - :git: https://github.com/speramusinc/SwiftUTI.git - -CHECKOUT OPTIONS: - SwiftUTI: - :commit: b6b46942fb3aad819610851f62a70e17a528444e - :git: https://github.com/speramusinc/SwiftUTI.git - SPEC CHECKSUMS: AFNetworking: b6f891fdfaed196b46c7a83cf209e09697b94057 cmark: 1d9ad0375e3b9fa281732e992467903606015520 @@ -174,17 +159,16 @@ SPEC CHECKSUMS: libbase58: 7c040313537b8c44b6e2d15586af8e21f7354efd libPhoneNumber-iOS: 0a32a9525cf8744fe02c5206eb30d571e38f7d75 MatomoTracker: 6f89e2561083685a360e223fb663e9ccd57c1d1a - MatrixKit: 3db15f216e2de4d53b1405434028d44f42d4af22 - MatrixSDK: f83bd3c5519c1cb9ac3998f6423574cf528f0250 + MatrixKit: 2ae83c621caf3c11681e00a0b802086ac4d1106c + MatrixSDK: caad3d93c665163d9d414f6876e9a7d220c6c46f OLMKit: 4ee0159d63feeb86d836fdcfefe418e163511639 Realm: 5a1d9d47bfc101dd597668b1a8af4288a2557f6d Reusable: 82be188f29d96dc5eff0db7b2393bcc08d2cdd5b SwiftGen: f872ca75cbd17bf7103c17f13dcfa0d9a15667b0 SwiftLint: fc9859e4e1752340664851f667bb1898b9c90114 - SwiftUTI: 917993c124f8eac25e88ced0202fc58d7eb50fa8 zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: 423280392fb3008372e2b92282dd0c08e4039c6f +PODFILE CHECKSUM: 6f647ad2f6c765719fd7e9a5d1e9e77e797d328f -COCOAPODS: 1.8.4 +COCOAPODS: 1.9.1