From 269914c2147715d3f7438a63c695b8c5f649f372 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 000000000..8b1378917 --- /dev/null +++ b/Riot/Assets/cy.lproj/Localizable.strings @@ -0,0 +1 @@ + From fe06ab7860915568a498c078e93a9cad9621458f 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 8b1378917..4e00dae82 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 c3b48e30da03081274dc77c566ae17daeb274da3 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 eea7f20a5..e706080d1 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 dab4646ec87edb99d897cec79358da1eee324788 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 88c5df6fd..0be574687 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 6dfafe67467f4f26f780f733dd8a53b6bb4f725a 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 4f466ccda..4ed0c9e98 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 82deec0f17ede65dddb348d67965d8199e4d914a 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 9c0ce9f95..f892b1bff 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 fa511623da4d84f7630bcdab981ef12819461ca8 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 f1666242d..a2216bde0 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 ee9e9b73f5b1a77e2db349084069a815bb3aeb04 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 f892b1bff..e158107b0 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 387170370cfb975283999c97bb58ea458c9e556b 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 41f81edf2..9246d1c2c 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 e460a13a14a0b1966b78384497e38bb164ef271d 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 4ed0c9e98..31ebfc3fe 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 c7168a618c5065085abf25a505c27ca641257aba 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 a2216bde0..022e59737 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 b5ab0bd3e267b55425cb88f0e4c1ec0f8e472e0c 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 022e59737..3fdfb3df4 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 6edc828d194152b075f46b83de821665d91d80d8 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 0be574687..90d153d91 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 1e5e9b738063e787baf7d6bcdd4690ee464f7dbb 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 5c6ca7b39..3f9f78ddf 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 598ce921a4e9ece2796941b9ae9e2713046fa4a1 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 31ebfc3fe..e7f4dfe46 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 d05c5f83ba12b443f5fcb53be29014d4152e91db 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 435cd1135..0328db012 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 58668ed7130e8c95fcbfbc014900940d84d19db8 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 e7f4dfe46..ce173345a 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 e0e71dfb1130a40703c8e8dc575d340c5c35d102 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 e158107b0..4035eae13 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 5f6bcf2cda5e8060e7eed86ba210eb90dc589289 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 3fdfb3df4..0a553b952 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 039c8e9ce075fad17e7be368c4e33ac4d683c16d 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 0328db012..cea12ce87 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 af25320d45abdae926b4e1b2d3624347f8e9fadf 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 4035eae13..8560ac8c6 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 ccd89f0828768130c6c2758c63d87c9eace380c7 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 e706080d1..9918d92a0 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 63f8212f49b5c1250e2eebd189d6d64329261245 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 8560ac8c6..4d5937378 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 c092008d629b7d14133eece3b166d68ff7bc33af 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 0a553b952..dd8d3669c 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 41671b9b794a570a7d9d0bbdece3edae855bdd22 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 ce173345a..906dacb63 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 4373b283bc1c7099559229b1729bc250c7434530 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 906dacb63..84603356f 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 046747cee7e764da85d6da3b5a5af602c209dc0d 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 4d5937378..ebcca5dcc 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 5e95188c51493c454ae3f771474c54894f6a4fe2 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 dd8d3669c..30b1e8d50 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 f400dcdf61aa9112c938955c0637af78f454cadf 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 9918d92a0..e07ff2de9 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 e1e82a7b5e2b5386adb607d40cd3bc65157711f0 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 30b1e8d50..90359ddb5 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 b2359df5683a734cff99eb2740e36d6132b5a238 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 f3312f925..431ee92d6 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 5b571892adfcfab23e28b9c5c416367d3e42062f 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 cea12ce87..cd2c527e9 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 5dce5557a5971efd3feed3b2e848f44e586c624e 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 000000000..da4a164c9 --- /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 000000000..91448ccb6 --- /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 000000000..cfcf9df66 --- /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 e1ebb800b..b3040c916 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 7913799cd..082e368e4 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 8d695c945..9ef0436e5 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 4409a74b6b9880399cb246dda60476ecdfec89b6 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 5a4652104..e455766e1 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 cf239ec78ab95fe94c10979748cafd685510a2af 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 7c34278ff..7f355f5b1 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 a89214ce4..c433215b8 100644 --- a/Riot/Modules/Room/CellData/RoomBubbleCellData.m +++ b/Riot/Modules/Room/CellData/RoomBubbleCellData.m @@ -62,26 +62,46 @@ static NSAttributedString *timestampVerticalWhitespace = nil; 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: { - self.tag = RoomBubbleCellDataTagRoomCreateWithPredecessor; + // 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: + { + 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 4149f7d2bebc6f3c2b15f05dac9367102a62c625 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 000000000..e9b657a03 --- /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 964085bb6db5b330c76498b80c97112afb041f64 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 000000000..e4c7a9bd8 --- /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 000000000..ac074f0ca --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellWithoutSenderInfoContentView.xib @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From f6e6816c02d3f6714b5cd14b3ac89de248d68d82 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 000000000..a280a979e --- /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 000000000..4b859339f --- /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 000000000..ca7a98a8c --- /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 000000000..6bf6b0a5d --- /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 000000000..4df053de6 --- /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 000000000..9cc1c0136 --- /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 000000000..91824fe16 --- /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 000000000..453407ced --- /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 000000000..c411b56ca --- /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 000000000..f1607fb4f --- /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 37fb9e7dfda2d5d0a8d4e07ca6527369f8fe05ed 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 2c4fac795..03ccc1656 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -353,6 +353,10 @@ [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 @@ { 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 d84e57690c50db35cd9ccd2766fcad41244439e5 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 103fd0acc..5b82ce4fe 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -284,7 +284,7 @@ ]]; } - MXKReceiptSendersContainer* avatarsContainer; + MXKReceiptSendersContainer* avatarsContainer; // Handle read receipts (if any) if (self.showBubbleReceipts && cellData.readReceipts.count && !isCollapsableCellCollapsed) @@ -349,47 +349,57 @@ { [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 da8ff63c550a32579dbe59ec10a10af19d3ea284 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 3f9f78ddf..4031f43d3 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 29a7763d180e0ebc9a104de69092b53d68f23cd5 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 3349c7ebf..c2153c78b 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 a55c0eae2..b86f1b7ef 100644 --- a/Riot/Model/Room/RoomPreviewData.m +++ b/Riot/Model/Room/RoomPreviewData.m @@ -51,12 +51,17 @@ 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 @@ } 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 e7cab93b4..09f15cc14 100644 --- a/Riot/Modules/Rooms/RoomsViewController.m +++ b/Riot/Modules/Rooms/RoomsViewController.m @@ -267,13 +267,12 @@ // 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 9f906b1acb630d1ba18211a0b045996ae352af66 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 6499f7712..3ab25b73b 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 5716bd1edf56d9a83057b93a1ba66394f6b42cc9 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 9cc1110f3..41d4c0aff 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 1f2b8b1ae382edb3c9c2c1f3d4cd17adeb7a6e15 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 2a4ce162c..f5f99249f 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 d2c15f0950cc18f2ef87def5285a7dab6be83b81 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 f3fe81265..272ade2ee 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 6c1e61eb53cb897bd5c310c02a36cb4b2588f62f 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 e215f2935..cf6bb7c59 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 23d05a38e537b426e384f6e3c5ef74e7e15145c7 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 f5f99249f..384289cbd 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 73d96cad172305d9542345e057ba9a16868af431 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 9246d1c2c..936014d80 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 fe3703fbafb4df8cdf0f91798bc84238ba6ea781 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 2ef812cdb..e3fdff348 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 3fdf41ea6aab982eaca26012a5abf98a2da1c607 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 8f17b3f77..d8060b9c8 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m @@ -240,6 +240,7 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou break; } + [self updateKeyBackupBanner]; [self forceRefresh]; } From fc1691fc925b9af52a0c129f213c65f5d3b1f3b8 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 3349c7ebf..73e014861 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 96d58bfcd6a1d640812d31c2c099df8644a3e98f 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 0117d6693..2b9f1c04d 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 305d37a19..6240b5928 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 16cbcdcdb952df4e59362bdf9a1629ee0fed897c 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 | 175 +++++++++++++++--- 2 files changed, 167 insertions(+), 25 deletions(-) diff --git a/Riot/Modules/Room/CellData/RoomBubbleCellData.h b/Riot/Modules/Room/CellData/RoomBubbleCellData.h index 7f355f5b1..62d27d998 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 c433215b8..af30837de 100644 --- a/Riot/Modules/Room/CellData/RoomBubbleCellData.m +++ b/Riot/Modules/Room/CellData/RoomBubbleCellData.m @@ -86,23 +86,11 @@ static NSAttributedString *timestampVerticalWhitespace = nil; } } 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 @@ static NSAttributedString *timestampVerticalWhitespace = nil; 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 @@ static NSAttributedString *timestampVerticalWhitespace = nil; - (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; + } } - - return [super addEvent:event andRoomState:roomState]; + + if (shouldAddEvent) + { + shouldAddEvent = [super addEvent:event andRoomState:roomState]; + } + + return shouldAddEvent; } +- (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 16d3ae66a5c5ebae0e767bc30bd65c0a68ed7e54 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 ca82723c6..ce1282065 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 077bfe8b1..6723f2dc0 100644 --- a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m +++ b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m @@ -31,6 +31,8 @@ NSString *const kMXKRoomBubbleCellRiotEditButtonPressed = @"kMXKRoomBubbleCellRi 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 @@ NSString *const kMXKRoomBubbleCellEventIdKey = @"kMXKRoomBubbleCellEventIdKey"; 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 3b217b8df291828541620a031c7b53e1722e6b83 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 be5547efb..c39876075 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 78dc28e744441d586b6f2c326efe8644f86c2b0b 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 3ba09e318..f9227d391 100644 --- a/Riot/Utils/EventFormatter.m +++ b/Riot/Utils/EventFormatter.m @@ -143,6 +143,13 @@ static NSString *const kEventFormatterTimeFormat = @"HH:mm"; } } + // 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 aeecf9db1f7445a0b9964f883c916f26acf918cf 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 e4c7a9bd8..158df123c 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 000000000..774a36c25 --- /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 ac074f0ca..000000000 --- a/Riot/Modules/Room/Views/BubbleCells/BaseContentViews/BubbleCellWithoutSenderInfoContentView.xib +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From ee49cefaa42cd33b76a9895e660db35c18260955 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 a280a979e..0344b20b9 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 22d350c479ec793dabf115520b068849d6e5d47b 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 4b859339f..1402be19f 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 ca7a98a8c..404d95945 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 6bf6b0a5d..41761b380 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 9cc1c0136..3093b5206 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 453407ced..b5af25a5a 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 senderId.isEmpty == false { - title = "Verification request" + if roomBubbleCellData.isIncoming { + title = VectorL10n.keyVerificationTileRequestIncomingTitle } else { - title = "Verification sent" + title = VectorL10n.keyVerificationTileRequestOutgoingTitle } - return KeyVerificationRequestStatusViewData(title: title, - senderId: senderId, - senderDisplayName: senderDisplayName, - statusText: statusText) + 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 let statusText = statusText { + viewData = KeyVerificationRequestStatusViewData(title: title, + senderId: senderId, + senderDisplayName: senderDisplayName, + statusText: statusText) + } else { + viewData = nil + } + + return viewData } } From 046e01ac9272460bfdf04c171e51a614c57fcd42 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 000000000..7cc1b8277 --- /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 000000000..6b4b529f1 --- /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 000000000..f9c0e6465 --- /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 aa00af2a0fd58153fc9756845b8fc63eefd3e6e5 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 03ccc1656..4053321be 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -354,8 +354,12 @@ [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 @@ { 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 @@ [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 @@ // 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 f910e4d3d7c9bade289c99c99286f713954cae96 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 1ecb646da..70984e2b9 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 517395fb0..df926b902 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -4826,6 +4826,32 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe } } +- (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 1c7dfda5a..fd01e5ad6 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 5b82ce4fe..280eef50d 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -35,6 +35,15 @@ 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 @@ [self reload]; }]; + + [self registerKeyVerificationRequestNotification]; + [self registerDeviceVerificationTransactionNotification]; } return self; } @@ -117,6 +129,16 @@ kThemeServiceDidChangeThemeNotificationObserver = nil; } + if (self.keyVerificationRequestDidChangeNotificationObserver) + { + [[NSNotificationCenter defaultCenter] removeObserver:self.keyVerificationRequestDidChangeNotificationObserver]; + } + + if (self.deviceVerificationTransactionDidChangeNotificationObserver) + { + [[NSNotificationCenter defaultCenter] removeObserver:self.deviceVerificationTransactionDidChangeNotificationObserver]; + } + [super destroy]; } @@ -189,6 +211,8 @@ { roomBubbleCellData.senderAvatarPlaceholder = [AvatarGenerator generateAvatarForMatrixItem:roomBubbleCellData.senderId withDisplayName:roomBubbleCellData.senderDisplayName]; } + + [self updateKeyVerificationIfNeededForRoomBubbleCellData:roomBubbleCellData]; UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath]; @@ -526,6 +550,173 @@ 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 @@ [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 eb37147d40a56896e116e2c9a65b3cc6f3c6832d 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 3687da88a..84dca9666 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 c809533a3..bdd170fb6 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 ece7185af..9e4e9309e 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 c70b8cc50..9f8ef8976 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 80ae30af7..a21cc9f31 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 ed9ddb2fa..47c7799a9 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)) - } - - }, failure: { [weak self] (error) in - guard let sself = self else { - return + sself.update(viewState: .errorMessage(VectorL10n.deviceVerificationErrorCannotLoadDevice)) } - 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 0121c0028..7808df654 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 d4a7967e5092644b33130582c44c12639c01d30e 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 fd3d454aa..fb93d2e5f 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 c5e679693ccda0e55b199b709c1aaab69194eddf 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 517395fb0..e986f3f00 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -4701,7 +4701,7 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe 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 ed9ddb2fa..b1dae3b0a 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 d84d669ec..89662ec22 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m @@ -406,7 +406,7 @@ 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 b73d7c2fe..ee8b6e916 100644 --- a/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.m +++ b/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.m @@ -151,7 +151,7 @@ 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 f3791dee562cb8dc427e78bccbf468df7bc48a44 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 2b9f1c04d..b647ea9fb 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 6240b5928..a45f06cda 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 41761b380..70353531f 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 e6f922f4b6eb5b60fca1ebc6c2b40d397a048028 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 b647ea9fb..e9618727f 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 0715718675da514a988e2ac244378543f910d1d9 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 e9618727f..668072889 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 0ec2c93edd095dce101f6077c4d8c5f4810bdc54 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 668072889..2870a0e01 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 c4bce89834a2186cf659fa79b25a1cd0a60e9fc8 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 e986f3f00..86e93d028 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -4706,7 +4706,7 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe 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 4d858639c..341a397b1 100644 --- a/Riot/Modules/EncryptionInfo/EncryptionInfoView.m +++ b/Riot/Modules/EncryptionInfo/EncryptionInfoView.m @@ -54,7 +54,7 @@ - (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 ab4c455e5..009654230 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m +++ b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m @@ -51,7 +51,7 @@ NSString *const kRoomEncryptedDataBubbleCellTapOnEncryptionIcon = @"kRoomEncrypt 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 ee8b6e916..162a0f682 100644 --- a/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.m +++ b/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.m @@ -154,7 +154,7 @@ [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 b13176cb1..d63ce69be 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 224b620b3..89bce3363 100644 --- a/Riot/Modules/UserDevices/UsersDevicesViewController.m +++ b/Riot/Modules/UserDevices/UsersDevicesViewController.m @@ -228,7 +228,7 @@ ofUser:deviceTableViewCell.deviceInfo.userId success:^{ - deviceTableViewCell.deviceInfo.verified = verificationStatus; + //deviceTableViewCell.deviceInfo.verified = verificationStatus; [self.tableView reloadData]; } failure:nil]; @@ -244,13 +244,13 @@ // 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 9ee73a778..41bf6485f 100644 --- a/Riot/Modules/UserDevices/Views/DeviceTableViewCell.m +++ b/Riot/Modules/UserDevices/Views/DeviceTableViewCell.m @@ -50,7 +50,7 @@ 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 @@ 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 22de7f4b81197b176179b52352887a74636080df 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 89bce3363..4bd8b21f9 100644 --- a/Riot/Modules/UserDevices/UsersDevicesViewController.m +++ b/Riot/Modules/UserDevices/UsersDevicesViewController.m @@ -227,14 +227,21 @@ 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 @@ 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 bce8eed74ec0675f54556e093f0d54b6b79daec9 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 cd2c527e9..ca88973a3 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 cd4baee387ff9af75ba8afd3ad478e1ed87ceb14 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 84603356f..57943357d 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 75b84174130b3a5b0ad1794a1eaf896f56d146ae 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 ebcca5dcc..d81a7508b 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 d1a1779970f2292ca1c2cb41e7e587746320432d 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 a45f06cda..0babab6a6 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 4bd8b21f9..82501d259 100644 --- a/Riot/Modules/UserDevices/UsersDevicesViewController.m +++ b/Riot/Modules/UserDevices/UsersDevicesViewController.m @@ -235,7 +235,7 @@ - (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 31d073978a929c73c835465a3c4968980aeb3241 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 2f04b8a93..984412ef4 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -237,6 +237,8 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe @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 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe // 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 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe [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 2870a0e01..a18e8f8ce 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 0babab6a6..d2028bb4e 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 aa0e7419bf7f0e97f28ad5374ffe5348df56b0ef 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 984412ef4..832d87787 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -1679,132 +1679,6 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe 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 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe 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 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe 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 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe { 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 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe 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 fc5cc015a..61d8a3e98 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 ccc82c87d461bfba983e6b9209fa004447ce77f9 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 90359ddb5..f0348542e 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 b99e45483e239e99bc5b54809f10ce29cdd65f9e 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 832d87787..85db64b6f 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -720,6 +720,11 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe - (void)keyVerificationRequestDidChangeNotification:(NSNotification *)notification { + if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground) + { + return; + } + NSDictionary *userInfo = notification.userInfo; MXKeyVerificationRequest *keyVerificationRequest = userInfo[MXDeviceVerificationManagerNotificationRequestKey]; From e608e64b7715f082612782a975a89d398e4c3fd3 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 a17db7e88..b39ed2681 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 21057b538ac8a77cf44353e6aa0ec4154c111393 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 7ad385fcb..7a273fa86 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 9a287fdeebbb785be8cd8d41ac09dde82fa8f725 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 1b800160b..7ca6c4d4a 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 76eaed59aec13df0ca8a87a47a6451e06f15c380 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 ca88973a3..8c4eed7a1 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 19cc11ac6e976ce4de9e205c281614b0f8004bec 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 57943357d..c85ff0853 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 51348c731d30279b5f4565502ff64a0d72d9e896 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 d81a7508b..08d9e3abb 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 ac06d9b7d5a723555f4affa3e52b8a52bd9676a0 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 f0348542e..2cbb8ad83 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 34844282b84415c7575c59eaaadb52fb3e71bb85 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 daa2aefdd..78d7e2fba 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 ea2bb93c56e3a34f9a219c3cdf9d22e4b04f3762 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 fd01e5ad6..ab09e2d2f 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 280eef50d..0b1ffbed9 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -44,6 +44,10 @@ // 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 @@ [self registerKeyVerificationRequestNotification]; [self registerDeviceVerificationTransactionNotification]; + [self registerTrustLevelDidChangeNotifications]; + + self.encryptionTrustLevel = RoomEncryptionTrustLevelUnknown; } return self; } @@ -105,6 +112,21 @@ 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 @@ } } +#pragma mark Encryption trust level + +- (void)registerTrustLevelDidChangeNotifications +{ + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceInfoTrustLevelDidChange:) name:MXDeviceInfoTrustLevelDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(crossSigningInfoTrustLevelDidChange:) name:MXCrossSigningInfoTrustLevelDidChangeNotification object:nil]; +} + +- (void)deviceInfoTrustLevelDidChange:(NSNotification*)notification +{ + MXDeviceInfo *deviceInfo = notification.object; + + NSString *userId = deviceInfo.userId; + + if (userId) + { + [self encryptionTrustLevelDidChangeRelatedToUserId:userId]; + } +} + +- (void)crossSigningInfoTrustLevelDidChange:(NSNotification*)notification +{ + MXCrossSigningInfo *crossSigningInfo = notification.object; + + NSString *userId = crossSigningInfo.userId; + + if (userId) + { + [self encryptionTrustLevelDidChangeRelatedToUserId:userId]; + } +} + +- (void)fetchEncryptionTrustedLevel +{ + [self encryptionTrustLevelDidChangeRelatedToUserId:self.mxSession.myUser.userId]; +} + +- (void)encryptionTrustLevelDidChangeRelatedToUserId:(NSString*)userId +{ + if (!self.room.summary.isEncrypted) + { + return; + } + + [self.room members:^(MXRoomMembers *roomMembers) { + MXRoomMember *roomMember = [roomMembers memberWithUserId:userId]; + + // If user belongs to the room refresh the trust level + if (roomMember) + { + [self.room trustedMembersProgressWithSuccess:^(NSProgress *trustedMembersProgress) { + + RoomEncryptionTrustLevel roomEncryptionTrustLevel; + + double trustedMembersPercentage = trustedMembersProgress.fractionCompleted; + + if (trustedMembersPercentage >= 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 682d53b60562aaa2dffdef3855c7defaf627eb4a 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 ff6a1b40d..e22e0422c 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 353189cdd..537c5168f 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 b175682f2a1b658a1ceed683e5932c0212dfa60a 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 009654230..bd022f430 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m +++ b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m @@ -23,60 +23,55 @@ NSString *const kRoomEncryptedDataBubbleCellTapOnEncryptionIcon = @"kRoomEncrypt + (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 @@ NSString *const kRoomEncryptedDataBubbleCellTapOnEncryptionIcon = @"kRoomEncrypt } 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 d96f8ab3e8ece3b1862ef140383ac258d7d6f4da 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 4053321be..9f58642a8 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 @@ 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 @@ 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 @@ [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 @@ // 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 @@ 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 @@ 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 855c21454..233ccab1d 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m @@ -128,8 +128,6 @@ 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 @@ } 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 bd79182c9153455d69f5f8be80ae39576d43d50c 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 3349c7ebf..d65df8187 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 2b6ae69a4c93c10497d0c1b2d7086dd16e103c97 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 0b1ffbed9..24da3dceb 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -238,18 +238,18 @@ // 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 124cdb2691bd18235fce4e81814c9073c64e9fca 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 24da3dceb..3bd0d0165 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -239,7 +239,7 @@ // 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 4e8a6832992c5d03814e6926a26654e3317094d7 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 08d9e3abb..19ecc7002 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 1fe53c2b9950907eeeaf2776a1c05947a0c70e85 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 9f58642a8..7da406e14 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -1647,6 +1647,9 @@ case RoomEncryptionTrustLevelTrusted: encryptionIconName = @"encryption_trusted"; break; + case RoomEncryptionTrustLevelUnknown: + encryptionIconName = @"encryption_normal"; + break; default: break; } From a647239d3fb3d33cf58877872d2518ddeb5c0022 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 d060cfcd8..40a5a18eb 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 04e3618bca691a89ac53ce07e8deb8d93790a5e0 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 e07ff2de9..573461056 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 d345f38926f8bb8827d57b27af74124b02f17b70 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 8c4eed7a1..7ed7ef4cc 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 989427bf20bafe93e18d09862f97680c8b1066f6 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 d65df8187..bbe790a13 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 4a6fe8088..ac1f27318 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -136,7 +136,6 @@ enum { 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> [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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> [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 aab92e77fd2a380136e6d22582fbcbe703539a76 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 85db64b6f..18234d6de 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -3840,38 +3840,34 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe { // 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 9656fb213..f224383d4 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 2ba421545..b0a604f80 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.m +++ b/Riot/Modules/Authentication/AuthenticationViewController.m @@ -1109,21 +1109,11 @@ 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 f8fc01553..a2106226e 100644 --- a/Riot/Modules/Contacts/Details/ContactDetailsViewController.m +++ b/Riot/Modules/Contacts/Details/ContactDetailsViewController.m @@ -1042,35 +1042,31 @@ } // 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 @@ 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 2c8a95226..9c7748244 100644 --- a/Riot/Modules/StartChat/StartChatViewController.m +++ b/Riot/Modules/StartChat/StartChatViewController.m @@ -573,35 +573,34 @@ 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 07f9bcdaa6751a92d05d254d654cad40f3fc447d 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 bcadaeb5f..3c8e1644a 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 1d845e23c0256b2eea71023c6a9f7cb9acfe1798 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 | 50 ++++++++++------- Riot/Categories/MXSession+Riot.h | 14 +++++ Riot/Categories/MXSession+Riot.m | 34 +++++++++++ .../Details/ContactDetailsViewController.m | 56 +++++++++++++------ .../StartChat/StartChatViewController.m | 56 ++++++++++++------- 5 files changed, 153 insertions(+), 57 deletions(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 18234d6de..ddde73e32 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -3841,24 +3841,7 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe // 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]; - - if (completion) - { - completion(); - } - - } failure:^(NSError *error) { - + void (^onFailure)(NSError *) = ^(NSError *error){ NSLog(@"[AppDelegate] Create direct chat failed"); //Alert user [self showErrorAsAlert:error]; @@ -3867,7 +3850,36 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe { completion(); } - }]; + }; + + [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 (canEnableE2E) + { + 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 9bc296a98..b907e64a1 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 3d8805b5e..1281f4db4 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 @@ 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 a2106226e..a6c968496 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 @@ { 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 9c7748244..806919e9d 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 @@ { // 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 stopActivityIndicator]; - - [[AppDelegate theDelegate] showRoom:room.roomId andEventId:nil withMatrixSession:self.mainSession]; - - } failure:^(NSError *error) { - - createBarButtonItem.enabled = YES; - - roomCreationRequest = nil; + self->roomCreationRequest = nil; [self stopActivityIndicator]; NSLog(@"[StartChatViewController] Create room failed"); // Alert user [[AppDelegate theDelegate] showErrorAsAlert:error]; + }; - }]; + [self.mainSession canEnableE2EByDefaultInNewRoomWithUsers:inviteArray success:^(BOOL canEnableE2E) { + MXStrongifyAndReturnIfNil(self); + + // 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; + + if (canEnableE2E && roomCreationParameters.invite3PIDArray == nil) + { + roomCreationParameters.initialStateEvents = @[ + [MXRoomCreationParameters initialStateEventForEncryptionWithAlgorithm:kMXCryptoMegolmAlgorithm + ]]; + } + + 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 266d5840e9d26639595a28695eaeb220f51d0ef0 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 bd022f430..a306c644b 100644 --- a/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m +++ b/Riot/Modules/Room/Views/BubbleCells/Encryption/RoomEncryptedDataBubbleCell.m @@ -37,10 +37,9 @@ NSString *const kRoomEncryptedDataBubbleCellTapOnEncryptionIcon = @"kRoomEncrypt 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 08b1659ff08e32ffa77834f1d0584ab5248e96ce 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 a18e8f8ce..863d24581 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 d2028bb4e..119a293e2 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 b98096849..9d351658d 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 ac1f27318..ca9430d3b 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -136,10 +136,8 @@ enum { 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> } } -- (void)toggleLabsDMKeyVerification:(id)sender -{ - UISwitch *switchButton = (UISwitch*)sender; - - RiotSettings.shared.enableDMKeyVerification = switchButton.isOn; -} - - (void)toggleLabsCrossSigning:(id)sender { UISwitch *switchButton = (UISwitch*)sender; From 7500ccd6a1d0b0cb4cb7880611a1e21f2e22423b 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 bbe790a13..2c9f973b5 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 d44b69a65..bc9a7c9c8 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 3306a7f7b..7d92e3309 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 6d300ab1a..ff6a9ad9d 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 b8706ec24d7907d71eda49037c22e25d0d435ca0 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 19ecc7002..b654549b9 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 63c876b6f6615b2f456a562ae7e9664d18cdd6d5 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 fb93d2e5f..f000c7d32 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 000000000..aed794102 --- /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 000000000..673f01543 --- /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 000000000..5844d146f --- /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 ca9430d3b..0bbd570ca 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 @@ enum { 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 @@ enum 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> { count = 1; } + else if (section == SETTINGS_SECTION_SECURITY_INDEX) + { + count = SECURITY_COUNT; + } + return count; } @@ -2555,6 +2568,17 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> { 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> [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 bb711f244..0c4d88b3d 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 7dd764031e833fb7e99379502f5fefa3cbeb538b 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 | 3493 +---------------- 3 files changed, 12 insertions(+), 3489 deletions(-) diff --git a/Riot/Modules/Settings/Security/Security.storyboard b/Riot/Modules/Settings/Security/Security.storyboard index aed794102..b3db6d0f2 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 673f01543..de3aefaa2 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 5844d146f..2eb9a2678 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 @@ NSString* const kSettingsViewControllerPhoneBookCountryCellId2 = @"kSettingsView 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> [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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> // 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> } } - [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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> }]; [self userInterfaceThemeDidChange]; - - self.signOutAlertPresenter = [SignOutAlertPresenter new]; - self.signOutAlertPresenter.delegate = self; } - (void)userInterfaceThemeDidChange @@ -522,9 +386,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> // 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> } } --(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,91 +741,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> } } -- (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]; @@ -1363,9 +793,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - // update the save button if there is an update - [self updateSaveButtonStatus]; - return SETTINGS_SECTION_COUNT; } @@ -1373,110 +800,7 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> { 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> count = keyBackupSection.numberOfRows; } } - else if (section == SETTINGS_SECTION_DEACTIVATE_ACCOUNT_INDEX) - { - count = 1; - } + return count; } @@ -1608,888 +929,7 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> 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 (section == SETTINGS_SECTION_DEVICES_INDEX) { if (row == DEVICES_DESCRIPTION_INDEX) { @@ -2577,103 +1017,13 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> { 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) + if (section == SETTINGS_SECTION_DEVICES_INDEX) { // Check whether this section is visible if (devicesArray.count > 0) @@ -2697,10 +1047,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> 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; } @@ -2716,24 +1062,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> } } -- (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 @@ -2765,85 +1093,14 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> - (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) @@ -2851,177 +1108,7 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> 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 (section == SETTINGS_SECTION_DEVICES_INDEX) { if (row > DEVICES_DESCRIPTION_INDEX) { @@ -3032,17 +1119,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> } } } - 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]; } @@ -3050,978 +1126,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> #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]; @@ -4085,426 +1189,7 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> } } -- (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 @@ -4525,21 +1210,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> [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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> 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 119acdfeca93ebb0ddee4151c4e905d3ad230117 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 | 495 ++++-------------- 2 files changed, 106 insertions(+), 392 deletions(-) diff --git a/Riot/Modules/Settings/Security/SecurityViewController.h b/Riot/Modules/Settings/Security/SecurityViewController.h index de3aefaa2..0ff4e9d97 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 2eb9a2678..ea34b5e3d 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 @@ enum 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> 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 @@ -208,18 +100,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> return viewController; } -//- (void)destroy -//{ -//// id notificationObserver = self.themeDidChangeNotificationObserver; -//// -//// if (notificationObserver) -//// { -//// [[NSNotificationCenter defaultCenter] removeObserver:notificationObserver]; -//// } -// -// [super destroy]; -//} - #pragma mark - View life cycle @@ -230,10 +110,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> // Setup `MXKViewControllerHandling` properties self.enableBarTintColorStatusChange = NO; self.rageShakeManager = [RageShakeManager sharedManager]; - - isSavingInProgress = NO; - isResetPwdInProgress = NO; - is3PIDBindingInProgress = NO; } - (void)viewDidLoad @@ -245,12 +121,8 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> // 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> - (void)destroy { - if (groupsDataSource) - { - groupsDataSource.delegate = nil; - [groupsDataSource destroy]; - groupsDataSource = nil; - } - // Release the potential pushed view controller [self releasePushedViewController]; @@ -331,30 +196,8 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> // 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]; + [self.tableView setContentOffset:CGPointMake(-self.tableView.mxk_adjustedContentInset.left, -self.tableView.mxk_adjustedContentInset.top) animated:YES]; + + }]; } - (void)viewWillDisappear:(BOOL)animated @@ -418,31 +252,7 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> [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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> { // 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> { [(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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> // 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> { // 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> initWithString:fingerprint ? fingerprint : @"" attributes:@{NSForegroundColorAttributeName : ThemeService.shared.theme.textPrimaryColor, NSFontAttributeName: [UIFont boldSystemFontOfSize:17]}]]; - + return cryptoInformationString; } @@ -586,15 +367,18 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> { // 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> { 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> attribute:NSLayoutAttributeTop multiplier:1.0f constant:0.0f]; - + NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:deviceView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual @@ -655,7 +437,7 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> attribute:NSLayoutAttributeLeft multiplier:1.0f constant:0.0f]; - + NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:deviceView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual @@ -663,7 +445,7 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> attribute:NSLayoutAttributeWidth multiplier:1.0f constant:0.0f]; - + NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:deviceView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual @@ -671,14 +453,12 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> { [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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> { // Keep ref on destinationViewController [super prepareForSegue:segue sender:sender]; - + // FIXME add night mode } @@ -799,7 +537,7 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { NSInteger count = 0; - + if (section == SETTINGS_SECTION_DEVICES_INDEX) { count = devicesArray.count; @@ -829,48 +567,20 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> else { cell.selectionStyle = UITableViewCellSelectionStyleDefault; - + cell.accessoryType = UITableViewCellAccessoryNone; cell.accessoryView = nil; } @@ -892,21 +602,21 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> // 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> if (row == CRYPTOGRAPHY_INFO_INDEX) { MXKTableViewCellWithTextView *cryptoCell = [self textViewCellForTableView:tableView atIndexPath:indexPath]; - + cryptoCell.mxkTextView.attributedText = [self cryptographyInformation]; cell = cryptoCell; @@ -980,7 +688,7 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> return NSLocalizedStringFromTable(@"settings_key_backup", @"Vector", nil); } } - + return nil; } @@ -1068,9 +776,9 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> - (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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> } } } - + [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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> [self deleteKeyExportFile]; // Show the export dialog - __weak typeof(self) weakSelf = self; + MXWeakify(self); [exportView showInViewController:self toExportKeysToFile:keyExportsFile onComplete:^(BOOL success) { + MXStrongifyAndReturnIfNil(self); - if (weakSelf) + self->currentAlert = nil; + self->exportView = nil; + + 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,21 +907,18 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> } } +- (void)toggleBlacklistUnverifiedDevices:(id)sender +{ + UISwitch *switchButton = (UISwitch*)sender; + + self.mainSession.crypto.globalBlacklistUnverifiedDevices = switchButton.on; + + [self.tableView reloadData]; +} #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 @@ -1305,11 +1020,11 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> - (void)showKeyBackupSetupFromSignOutFlow:(BOOL)showFromSignOutFlow { keyBackupSetupCoordinatorBridgePresenter = [[KeyBackupSetupCoordinatorBridgePresenter alloc] initWithSession:self.mainSession]; - + [keyBackupSetupCoordinatorBridgePresenter presentFrom:self isStartedFromSignOut:showFromSignOutFlow animated:true]; - + keyBackupSetupCoordinatorBridgePresenter.delegate = self; } From 3cfdbf0e0efcfede92b79b1e998a728a6c3e494d 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 ea34b5e3d..da40082ec 100644 --- a/Riot/Modules/Settings/Security/SecurtiyViewController.m +++ b/Riot/Modules/Settings/Security/SecurtiyViewController.m @@ -366,10 +366,8 @@ UIDocumentInteractionControllerDelegate> - (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 @@ UIDocumentInteractionControllerDelegate> 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 087e6173b2f97f83f29b6f41b724ef3fbe4315cd 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 | 247 +++++++++--------- .../Modules/Settings/SettingsViewController.m | 4 +- 4 files changed, 170 insertions(+), 132 deletions(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 863d24581..c82e8a04c 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 119a293e2..2a7d5a0bd 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 da40082ec..27bf4a4f8 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 @@ UIDocumentInteractionControllerDelegate> [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 @@ UIDocumentInteractionControllerDelegate> - (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) + switch (section) { - 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) - { + 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 @@ UIDocumentInteractionControllerDelegate> 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 @@ UIDocumentInteractionControllerDelegate> cell.backgroundColor = [UIColor redColor]; MXSession* session = self.mainSession; - if (section == SETTINGS_SECTION_DEVICES_INDEX) + if (section == SECTION_SESSIONS) { - if (row == DEVICES_DESCRIPTION_INDEX) + if (row < devicesArray.count) { - 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; + 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 (row == devicesArray.count) + { + cell = [self descriptionCellForTableView:tableView + withText:NSLocalizedStringFromTable(@"security_settings_sessions_description", @"Vector", nil) ]; + } } - else if (section == SETTINGS_SECTION_CRYPTOGRAPHY_INDEX) + else if (section == SECTION_ADVANCED) { - if (row == CRYPTOGRAPHY_INFO_INDEX) + switch (row) { - 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 = 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) + case ADVANCED_BLACKLIST_UNVERIFIED_DEVICES: { - exportKeysBtnCell = [[MXKTableViewCellWithButton alloc] init]; + 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; } - 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) + switch (section) { - // 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); - } + 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 @@ UIDocumentInteractionControllerDelegate> - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { + if (section == SECTION_SESSIONS) + { + return 44; + } return 24; } @@ -807,15 +797,12 @@ UIDocumentInteractionControllerDelegate> 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 @@ UIDocumentInteractionControllerDelegate> - (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 0bbd570ca..dc32aeb6a 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -2574,7 +2574,7 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> { 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> } 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 0a7bd8fb61aa2b0b563b8d48e9030d80147d11c3 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 27bf4a4f8..5f8c6b8de 100644 --- a/Riot/Modules/Settings/Security/SecurtiyViewController.m +++ b/Riot/Modules/Settings/Security/SecurtiyViewController.m @@ -578,9 +578,9 @@ UIDocumentInteractionControllerDelegate> 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 @@ UIDocumentInteractionControllerDelegate> 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 @@ UIDocumentInteractionControllerDelegate> { 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 045ea992f1d797e4444e23425fccaf7ba175d07f 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 5f8c6b8de..83e2f5506 100644 --- a/Riot/Modules/Settings/Security/SecurtiyViewController.m +++ b/Riot/Modules/Settings/Security/SecurtiyViewController.m @@ -44,6 +44,13 @@ enum { ADVANCED_COUNT }; +enum { + DEBUG_CRYPTO_INFO, + DEBUG_CROSSSIGNING_INFO, + DEBUG_CROSSSIGNING_BOOTSTRAP, + DEBUG_COUNT +}; + @interface SecurityViewController () < MXKDataSourceDelegate, @@ -359,6 +366,42 @@ UIDocumentInteractionControllerDelegate> 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 @@ UIDocumentInteractionControllerDelegate> count = ADVANCED_COUNT; break; case SECTION_DEBUG: - count = 1; + count = DEBUG_COUNT; break; } @@ -705,8 +748,8 @@ UIDocumentInteractionControllerDelegate> } 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 @@ UIDocumentInteractionControllerDelegate> } 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 @@ UIDocumentInteractionControllerDelegate> { // 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 2e20508ce48b2a333ca2290271c4547f5de00650 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 f000c7d32..d002fba7c 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 c82e8a04c..359af0327 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 2a7d5a0bd..7a209d835 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 000000000..3292716a4 --- /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 000000000..001ceefc0 --- /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 000000000..e10d9c3e8 --- /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 83e2f5506..4d7791898 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 @@ UIDocumentInteractionControllerDelegate> NSUInteger deviceIndex = row; if (deviceIndex < devicesArray.count) { - [self showDeviceDetails:devicesArray[deviceIndex]]; + ManageSessionViewController *viewController = [ManageSessionViewController instantiateWithMatrixSession:self.mainSession andDevice:devicesArray[deviceIndex]]; + + [self pushViewController:viewController]; } } From ce21a4b6d72104455451934796da73de17df1bcf 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 e10d9c3e8..5d202031a 100644 --- a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m +++ b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m @@ -327,6 +327,23 @@ MXKEncryptionInfoViewDelegate> [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 @@ MXKEncryptionInfoViewDelegate> UITableViewCell *cell = [[UITableViewCell alloc] init]; cell.backgroundColor = [UIColor redColor]; - MXSession* session = self.mainSession; switch (section) { case SECTION_SESSION_INFO: @@ -539,6 +555,7 @@ MXKEncryptionInfoViewDelegate> 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 @@ MXKEncryptionInfoViewDelegate> { 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 @@ MXKEncryptionInfoViewDelegate> #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 d485ed6ad696d53547d923e81883d106fdc484a4 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 5d202031a..11e4b1372 100644 --- a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m +++ b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m @@ -266,61 +266,6 @@ MXKEncryptionInfoViewDelegate> } } -- (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 @@ MXKEncryptionInfoViewDelegate> { 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]; + [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; - [deactivateAccountBtnCell.mxkButton removeTarget:self action:nil forControlEvents:UIControlEventTouchUpInside]; - //[deactivateAccountBtnCell.mxkButton addTarget:self action:@selector(deactivateAccountAction) forControlEvents:UIControlEventTouchUpInside]; - deactivateAccountBtnCell.mxkButton.accessibilityIdentifier = nil; - - cell = deactivateAccountBtnCell; + cell = removeSessionBtnCell; break; } } @@ -693,13 +636,23 @@ MXKEncryptionInfoViewDelegate> 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 @@ MXKEncryptionInfoViewDelegate> NSLog(@"[ManageSessionVC] Rename device (%@) failed", self->device.deviceId); [self reloadDeviceWithCompletion:^{ [self.activityIndicator stopAnimating]; + [[AppDelegate theDelegate] showErrorAsAlert:error]; }]; }]; @@ -763,6 +717,104 @@ MXKEncryptionInfoViewDelegate> [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 60748eaf0afe1924527564d57ea58bbe2d5cd1de 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 4d7791898..694c0ecbf 100644 --- a/Riot/Modules/Settings/Security/SecurtiyViewController.m +++ b/Riot/Modules/Settings/Security/SecurtiyViewController.m @@ -56,9 +56,7 @@ enum { @interface SecurityViewController () < MXKDataSourceDelegate, -MXKDeviceViewDelegate, SettingsKeyBackupTableViewSectionDelegate, -MXKEncryptionInfoViewDelegate, KeyBackupSetupCoordinatorBridgePresenterDelegate, KeyBackupRecoverCoordinatorBridgePresenterDelegate, UIDocumentInteractionControllerDelegate> @@ -68,7 +66,6 @@ UIDocumentInteractionControllerDelegate> // Devices NSMutableArray *devicesArray; - DeviceView *deviceView; // Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar. id kAppDelegateDidTapStatusBarNotificationObserver; @@ -306,12 +303,6 @@ UIDocumentInteractionControllerDelegate> { // Remove observers [[NSNotificationCenter defaultCenter] removeObserver:self]; - - if (deviceView) - { - [deviceView removeFromSuperview]; - deviceView = nil; - } } - (void)loadCurrentDeviceInformation @@ -452,110 +443,12 @@ UIDocumentInteractionControllerDelegate> }]; } -- (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 8dbb4c229c836e9df5fb5de59a30b62d45586b00 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 4bce2072b..096bd6241 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 dc32aeb6a..1fbb18f38 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -64,9 +64,6 @@ enum 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 @@ enum 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 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(void); @interface SettingsViewController () NSInteger localContactsSyncIndex; NSInteger localContactsPhoneBookCountryIndex; - // Devices - NSMutableArray *devicesArray; - DeviceView *deviceView; - // Flair: the groups data source GroupsDataSource *groupsDataSource; @@ -249,14 +227,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> // 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; @@ -264,9 +234,7 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> // The current pushed view controller UIViewController *pushedViewController; - SettingsKeyBackupTableViewSection *keyBackupSection; KeyBackupSetupCoordinatorBridgePresenter *keyBackupSetupCoordinatorBridgePresenter; - KeyBackupRecoverCoordinatorBridgePresenter *keyBackupRecoverCoordinatorBridgePresenter; SettingsIdentityServerCoordinatorBridgePresenter *identityServerSettingsCoordinatorBridgePresenter; } @@ -365,18 +333,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> { [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]; @@ -440,13 +396,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> // Release the potential pushed view controller [self releasePushedViewController]; - if (documentInteractionController) - { - [documentInteractionController dismissPreviewAnimated:NO]; - [documentInteractionController dismissMenuAnimated:NO]; - documentInteractionController = nil; - } - if (kThemeServiceDidChangeThemeNotificationObserver) { [[NSNotificationCenter defaultCenter] removeObserver:kThemeServiceDidChangeThemeNotificationObserver]; @@ -475,7 +424,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> } keyBackupSetupCoordinatorBridgePresenter = nil; - keyBackupRecoverCoordinatorBridgePresenter = nil; identityServerSettingsCoordinatorBridgePresenter = nil; } @@ -510,13 +458,7 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> // 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) { @@ -645,12 +587,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> [[NSNotificationCenter defaultCenter] removeObserver:self]; onReadyToDestroyHandler = nil; - - if (deviceView) - { - [deviceView removeFromSuperview]; - deviceView = nil; - } } -(void)setNewEmailEditingEnabled:(BOOL)newEmailEditingEnabled @@ -999,171 +935,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> }]; } -- (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]) @@ -1462,31 +1233,6 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> } } } - 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> [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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> } } } - 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> 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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> [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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> [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 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> - (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 46a6979ee394bf6c35053d40c19c504d7d5456b6 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 11e4b1372..de16eed1d 100644 --- a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m +++ b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m @@ -136,7 +136,7 @@ MXKEncryptionInfoViewDelegate> if (self.tableView.dataSource) { - [self refreshSettings]; + [self reloadData]; } } @@ -190,7 +190,7 @@ MXKEncryptionInfoViewDelegate> [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 @@ MXKEncryptionInfoViewDelegate> } } -- (void)refreshSettings +- (void)reloadData { // Trigger a full table reloadData [self.tableView reloadData]; @@ -279,53 +279,16 @@ MXKEncryptionInfoViewDelegate> 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 @@ MXKEncryptionInfoViewDelegate> // 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 @@ MXKEncryptionInfoViewDelegate> - (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 694c0ecbf..111fabae6 100644 --- a/Riot/Modules/Settings/Security/SecurtiyViewController.m +++ b/Riot/Modules/Settings/Security/SecurtiyViewController.m @@ -165,7 +165,7 @@ UIDocumentInteractionControllerDelegate> if (self.tableView.dataSource) { - [self refreshSettings]; + [self reloadData]; } } @@ -229,7 +229,7 @@ UIDocumentInteractionControllerDelegate> [self releasePushedViewController]; // Refresh display - [self refreshSettings]; + [self reloadData]; // Refresh the current device information in parallel [self loadCurrentDeviceInformation]; @@ -313,7 +313,7 @@ UIDocumentInteractionControllerDelegate> // 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 @@ UIDocumentInteractionControllerDelegate> // 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 @@ UIDocumentInteractionControllerDelegate> - (void)dataSource:(MXKDataSource *)dataSource didCellChange:(id)changes { // Group data has been updated. Do a simple full reload - [self refreshSettings]; + [self reloadData]; } From dc0fb2024f1ba5d70c0c4b410b3bbab1646165b9 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 359af0327..06a6177b2 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 7a209d835..388a956d7 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -2646,18 +2646,18 @@ internal enum VectorL10n { internal static var securitySettingsBlacklistUnverifiedDevicesDescription: String { return VectorL10n.tr("Vector", "security_settings_blacklist_unverified_devices_description") } + /// MY 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 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") } - /// 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") diff --git a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.h b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.h index 001ceefc0..c61242f7a 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 de16eed1d..be916dffb 100644 --- a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m +++ b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m @@ -47,21 +47,13 @@ enum { }; -@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 @@ MXKEncryptionInfoViewDelegate> 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 @@ MXKEncryptionInfoViewDelegate> } } -- (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 @@ MXKEncryptionInfoViewDelegate> [currentAlert dismissViewControllerAnimated:NO completion:nil]; currentAlert = nil; } - - if (kAppDelegateDidTapStatusBarNotificationObserver) - { - [[NSNotificationCenter defaultCenter] removeObserver:kAppDelegateDidTapStatusBarNotificationObserver]; - kAppDelegateDidTapStatusBarNotificationObserver = nil; - } } #pragma mark - Internal methods @@ -258,12 +218,6 @@ MXKEncryptionInfoViewDelegate> { // Remove observers [[NSNotificationCenter defaultCenter] removeObserver:self]; - - if (deviceView) - { - [deviceView removeFromSuperview]; - deviceView = nil; - } } - (void)reloadData @@ -778,12 +732,4 @@ MXKEncryptionInfoViewDelegate> }]; } -#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 0ff4e9d97..1452f3144 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 111fabae6..422562218 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 @@ enum { @interface SecurityViewController () < -MXKDataSourceDelegate, SettingsKeyBackupTableViewSectionDelegate, KeyBackupSetupCoordinatorBridgePresenterDelegate, KeyBackupRecoverCoordinatorBridgePresenterDelegate, @@ -67,9 +66,6 @@ UIDocumentInteractionControllerDelegate> // 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 @@ UIDocumentInteractionControllerDelegate> 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 @@ UIDocumentInteractionControllerDelegate> 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 @@ UIDocumentInteractionControllerDelegate> // 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 @@ UIDocumentInteractionControllerDelegate> [currentAlert dismissViewControllerAnimated:NO completion:nil]; currentAlert = nil; } - - if (kAppDelegateDidTapStatusBarNotificationObserver) - { - [[NSNotificationCenter defaultCenter] removeObserver:kAppDelegateDidTapStatusBarNotificationObserver]; - kAppDelegateDidTapStatusBarNotificationObserver = nil; - } } #pragma mark - Internal methods @@ -472,7 +436,7 @@ UIDocumentInteractionControllerDelegate> switch (section) { - case SECTION_SESSIONS: + case SECTION_CRYPTO_SESSIONS: count = devicesArray.count + 1; break; case SECTION_KEYBACKUP: @@ -597,7 +561,7 @@ UIDocumentInteractionControllerDelegate> 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 @@ UIDocumentInteractionControllerDelegate> 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 @@ UIDocumentInteractionControllerDelegate> { 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 @@ UIDocumentInteractionControllerDelegate> - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { - if (section == SECTION_SESSIONS) + if (section == SECTION_CRYPTO_SESSIONS) { return 44; } @@ -792,7 +756,7 @@ UIDocumentInteractionControllerDelegate> 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 @@ UIDocumentInteractionControllerDelegate> } -#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 1fbb18f38..a65477d66 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -2661,6 +2661,17 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> } } } + 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 620952ecab8a796c7b626881a85f277446162428 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 d002fba7c..efad11f47 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 d8948ff155bcbc190cb7bbcf874bce93c56835d0 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 c85ff0853..41cfb59eb 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 6475d072285a8ef9d31f0ef87c049a7c4fa4e02f 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 2cbb8ad83..39d9f1af1 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 59fd9e12b57c6023b7413225bb9b54802872d3fe 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 b907e64a1..9bc296a98 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 1281f4db4..0f2f9782d 100644 --- a/Riot/Categories/MXSession+Riot.m +++ b/Riot/Categories/MXSession+Riot.m @@ -49,37 +49,4 @@ 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 cb2398da48895f3e7b86a2e227d30690c1e2b945 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 3bd0d0165..93b77ad81 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -205,7 +205,7 @@ if (userId) { - [self encryptionTrustLevelDidChangeRelatedToUserId:userId]; + [self encryptionTrustLevelDidChangeRelatedToUserId:userId forceDownload:NO]; } } @@ -217,16 +217,16 @@ 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 @@ // 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 50d146c6cd39611da6fe5d77ba4f96ba9da4680b 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 000000000..76910a825 --- /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 000000000..2f59b1973 --- /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 000000000..3d0e50762 --- /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 000000000..5af56d06b --- /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 000000000..b57301d75 --- /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 000000000..4ef9b3e0d --- /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 000000000..f50a4ccb8 --- /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 000000000..efbb54c56 --- /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 458a8952d195dd993861bc2834ace8a902d67105 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 000000000..3a35dab77 --- /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 000000000..2808bd804 --- /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 000000000..6c3f6b345 --- /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 000000000..a24c78be7 --- /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 000000000..8d32b3868 --- /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 000000000..621652cf6 --- /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 000000000..178d0e415 --- /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 000000000..f1eb8f1c6 --- /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 751cf92d89afefbec9a5557c8a976e256422e674 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 000000000..ae9471461 --- /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 c39876075..5aeadc7d1 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 26813e0379a63777381b155db7d4e938ec27ac36 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 93b77ad81..175cff69a 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -245,7 +245,8 @@ double trustedDevicesPercentage = usersTrustLevelSummary.trustedDevicesProgress.fractionCompleted; - if (trustedDevicesPercentage >= 1.0) + if (trustedDevicesPercentage >= 1.0 + || usersTrustLevelSummary.trustedDevicesProgress.totalUnitCount == 0) { roomEncryptionTrustLevel = RoomEncryptionTrustLevelTrusted; } From a2a9e78b2f23c66006e31346cdd0c243f792124b 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 000000000..f281374f7 --- /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 000000000..0ee3f3231 --- /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 000000000..b2d19c91c --- /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 000000000..46eccaaa5 --- /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 000000000..8487612ec --- /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 000000000..2d5c915f0 --- /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 000000000..f99a7e107 --- /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 000000000..27ecd1c09 --- /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 000000000..4df41221d --- /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 000000000..742938c02 --- /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 0e1a60389e78f99df4e12d8e64b278571f802166 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 000000000..5bda5e45f --- /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 000000000..929449c77 --- /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 000000000..feeba8d3b --- /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 6baec47dfd42da74ab1679e2ae7bfdd11ef18795 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 84dca9666..b3ffdc89e 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 bdd170fb6..8176b655b 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 11eb717bb8e26cdd462d9cb7c16f5be39fddfd59 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 70984e2b9..b3091d4bc 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 ddde73e32..35b5236cd 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -4889,6 +4889,29 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe 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 0e0bc5557e4dcf82c3d6af46db2850718822601c 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 | 330 +++++++++++++++--- .../RoomMemberDetailsViewController.xib | 18 +- 5 files changed, 297 insertions(+), 76 deletions(-) diff --git a/Riot/AppDelegate.h b/Riot/AppDelegate.h index b3091d4bc..33f2ade6d 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 2666caf2f..633ae0a98 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 dd318b926..9881e328b 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 89662ec22..7322ce27a 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 @@ */ 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 @@ // 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 @@ [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 @@ // 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 @@ 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,39 +438,134 @@ } // Retrieve member's devices - NSString *userId = self.mxRoomMember.userId; __weak typeof(self) weakSelf = self; - - [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]; - } - - }]; + + 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]; } +- (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) + { + 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); + }]; + } + 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; + } + + 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 - (void)hideNavigationBarBorder:(BOOL)isHidden @@ -484,7 +614,7 @@ [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 @@ } } - 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)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 @@ - (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 *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 @@ } } -- (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 @@ - (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 b03f8eec4..b723cb4f4 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 d3648cfcb4e22578b5ebba5849e895174f4e581e 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 efad11f47..eb5802c59 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 7ec5424ed..66ef12a4b 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 17ae756c640fade5d4b25b336519d3a942edc1af 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 2c9f973b5..7e311f7ab 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 ddde73e32..522c38bc1 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -2801,6 +2801,9 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe 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 a65477d66..dd5c79285 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -3056,6 +3056,8 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> UISwitch *switchButton = (UISwitch*)sender; RiotSettings.shared.enableCrossSigning = switchButton.isOn; + + self.mainSession.crypto.warnOnUnknowDevices = !RiotSettings.shared.enableCrossSigning; } - (void)togglePinRoomsWithMissedNotif:(id)sender From 05e1747a779e5ebdca0b69943fb6c8890af1d90b 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 35b5236cd..6e7523bba 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -4901,7 +4901,7 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe 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 e356c50470fc320046b149a9ee2207c1828353cf 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 f50a4ccb8..ce8feb50a 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 885c26292e056fa5a4d9b2ab142dcdc7077c98b6 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 3bd0d0165..f74b64657 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -238,9 +238,10 @@ // 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 7322ce27a..3c49b1ef0 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m @@ -484,31 +484,27 @@ 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 3442a699240cc672fc483492be97560746da1c29 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 | 80 +++---------------- .../Listing/Views/RecentRoomTableViewCell.m | 38 ++++++++- 9 files changed, 170 insertions(+), 81 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 2c9f973b5..a1ec981a5 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 ddde73e32..779442d16 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -266,6 +266,7 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe // 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 804a6a6be..c69617bf5 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 0367f2436..fb13dccc4 100644 --- a/Riot/Categories/MXRoomSummary+Riot.m +++ b/Riot/Categories/MXRoomSummary+Riot.m @@ -47,4 +47,31 @@ 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 1f7e1b443..a33e0e5e0 100644 --- a/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m +++ b/Riot/Modules/Common/Recents/Views/RecentTableViewCell.m @@ -137,7 +137,15 @@ static const CGFloat kDirectRoomBorderWidth = 3.0; 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 @@ static const CGFloat kDirectRoomBorderWidth = 3.0; 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 5c6ed3aa3..8bd307ecf 100644 --- a/Riot/Modules/Home/Views/RoomCollectionViewCell.m +++ b/Riot/Modules/Home/Views/RoomCollectionViewCell.m @@ -159,7 +159,15 @@ static const CGFloat kDirectRoomBorderWidth = 3.0; 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 @@ static const CGFloat kDirectRoomBorderWidth = 3.0; 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 ab09e2d2f..37ec55cbf 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 175cff69a..e38abb761 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -112,7 +112,7 @@ NSLog(@"[MXKRoomDataSource] finalizeRoomDataSource: Cannot retrieve all room members"); }]; } - + if (self.room.summary.isEncrypted) { [self fetchEncryptionTrustedLevel]; @@ -193,85 +193,27 @@ - (void)registerTrustLevelDidChangeNotifications { - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceInfoTrustLevelDidChange:) name:MXDeviceInfoTrustLevelDidChangeNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(crossSigningInfoTrustLevelDidChange:) name:MXCrossSigningInfoTrustLevelDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(roomSummaryDidChange:) name:kMXRoomSummaryDidChangeNotification object:self.room.summary]; } -- (void)deviceInfoTrustLevelDidChange:(NSNotification*)notification -{ - MXDeviceInfo *deviceInfo = notification.object; - - NSString *userId = deviceInfo.userId; - - if (userId) - { - [self encryptionTrustLevelDidChangeRelatedToUserId:userId forceDownload:NO]; - } -} -- (void)crossSigningInfoTrustLevelDidChange:(NSNotification*)notification -{ - MXCrossSigningInfo *crossSigningInfo = notification.object; - - NSString *userId = crossSigningInfo.userId; - - if (userId) - { - [self encryptionTrustLevelDidChangeRelatedToUserId:userId forceDownload:NO]; - } -} - -- (void)fetchEncryptionTrustedLevel -{ - [self encryptionTrustLevelDidChangeRelatedToUserId:self.mxSession.myUser.userId forceDownload:YES]; -} - -- (void)encryptionTrustLevelDidChangeRelatedToUserId:(NSString*)userId forceDownload:(BOOL)forceDownload +- (void)roomSummaryDidChange:(NSNotification*)notification { if (!self.room.summary.isEncrypted) { return; } - [self.room members:^(MXRoomMembers *roomMembers) { - MXRoomMember *roomMember = [roomMembers memberWithUserId:userId]; - - // If user belongs to the room refresh the trust level - if (roomMember) - { - [self.room membersTrustLevelSummaryWithForceDownload:forceDownload success:^(MXUsersTrustLevelSummary *usersTrustLevelSummary) { - - RoomEncryptionTrustLevel roomEncryptionTrustLevel; - - double trustedDevicesPercentage = usersTrustLevelSummary.trustedDevicesProgress.fractionCompleted; - - if (trustedDevicesPercentage >= 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"); - }]; + [self fetchEncryptionTrustedLevel]; } +- (void)fetchEncryptionTrustedLevel +{ + self.encryptionTrustLevel = self.room.summary.roomEncryptionTrustLevel; + [self.roomDataSourceDelegate roomDataSource:self didUpdateEncryptionTrustLevel:self.encryptionTrustLevel]; +} + + #pragma mark - - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section diff --git a/RiotShareExtension/Modules/Share/Listing/Views/RecentRoomTableViewCell.m b/RiotShareExtension/Modules/Share/Listing/Views/RecentRoomTableViewCell.m index 4796cc841..48db29100 100644 --- a/RiotShareExtension/Modules/Share/Listing/Views/RecentRoomTableViewCell.m +++ b/RiotShareExtension/Modules/Share/Listing/Views/RecentRoomTableViewCell.m @@ -71,7 +71,15 @@ 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 @@ 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 924eed21a340ff0cc14dbe43001a6cac58dad399 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 a844bd34c..d27ac204d 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 edafecee2..c32ea4e8e 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 f23bdddbc1deb39b371649c566da75a0f480a335 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 8d32b3868..748421b2e 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 2d5c915f0..24f2aed27 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 f99a7e107..21c298dce 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 57568fc76979bf12009882d6675bb7d96b0dcb0d 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 eb5802c59..dcfda5940 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 b3ffdc89e..476cf80cd 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 000000000..d9d8f29fe --- /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 a21cc9f31..1d6a465ad 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 b9ebde0b5..a583410bd 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 4b5c83580..e3793c883 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 7869f9a31..69065a751 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 2be7640a9..86c26a7c2 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 8ea8aa449..9c5167f4f 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 7d1780062..e609f6b38 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 3d942ea8f2e91fb8b155ca782d8fb73e2d563edb 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 76910a825..ffc2ce99f 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 4ef9b3e0d..a98367537 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 5bda5e45f..8fcd1c239 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 deviceVerificationCoordinator = DeviceVerificationCoordinator(navigationRouter: self.navigationRouter, session: self.session, userId: self.userId, otherDeviceId: deviceId) + deviceVerificationCoordinator.delegate = self + deviceVerificationCoordinator.start() - let encryptionInfoView: EncryptionInfoView = EncryptionInfoView(deviceInfo: deviceInfo, andMatrixSession: session) - encryptionInfoView.delegate = self + self.add(childCoordinator: deviceVerificationCoordinator) - // 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) + 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 065f86a31601a72a6217f0162715fce29a975d95 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 41cfb59eb..03c59fc0a 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 b7ccbd326f20f9580b5fb991aa1eb8d161a3e885 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 0303e1aca..8fd41d271 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 b0c964fa1..ea0162bc9 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 c7a4f1662c0b9a25a9e267ac958a11e842fb26f6 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 748421b2e..c358c4770 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 8fcd1c239..3efcb6eab 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 929449c77..af19e72f3 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 a0613247c3b749519e31143235da4576e0d7b34a 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 be916dffb..328e54a7d 100644 --- a/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m +++ b/Riot/Modules/Settings/Security/ManageSession/ManageSessionViewController.m @@ -62,6 +62,8 @@ enum { UIViewController *pushedViewController; } +@property (nonatomic, strong) UserVerificationCoordinatorBridgePresenter *userVerificationCoordinatorBridgePresenter; + @end @implementation ManageSessionViewController @@ -636,7 +638,13 @@ enum { - (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 7f93dc68b736b5e6b93e4d636ad717499fff7006 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 fb13dccc4..688f9d2c8 100644 --- a/Riot/Categories/MXRoomSummary+Riot.m +++ b/Riot/Categories/MXRoomSummary+Riot.m @@ -52,22 +52,25 @@ 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 8ffec2b989ce812b2b4a0545bad647238c6a7664 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 7ed7ef4cc..8c7879fa5 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 9e3910ce7a37a6b34785df973da6e4d80c53ac82 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 b654549b9..6ec5f5391 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 396bf25825752c0e146d0af66ed1918a73490b41 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 06a6177b2..b2da90f55 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 388a956d7..8ba003320 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 08444abfc9ada98a0be3808ccddacabe109a9d0f 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 3c49b1ef0..2061b3510 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m @@ -130,6 +130,7 @@ 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 memberPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mxRoomMember.userId]; NSInteger oneSelfPowerLevel = [powerLevels powerLevelOfUserWithUserID:self.mainSession.myUser.userId]; + [securityActionsArray removeAllObjects]; [adminActionsArray removeAllObjects]; [otherActionsArray removeAllObjects]; @@ -744,9 +746,29 @@ } } + 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 @@ { 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 @@ { if (section == securityIndex) { - return @"SECURITY"; + return NSLocalizedStringFromTable(@"room_participants_action_section_security", @"Vector", nil); } else if (section == adminToolsIndex) { @@ -823,7 +833,7 @@ } 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 *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 @@ 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 @@ 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 1717beb063ebc3c52d47109adf27e224ff650de6 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 a583410bd..7c6aad04c 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 69065a751..6adef540e 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 c358c4770..eb8d2ff65 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 f281374f7..d0b6de6e7 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 21c298dce..b1384ba1f 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 b57301d75..aa0e901f7 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 94a28e443a8f4e45dab7d7d967920eb08f11bebc 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 eb8d2ff65..01cdc04e0 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 8938c9c215ceeec4d339755748addc09997fc63f 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 4da7fa074..9d02bbc2d 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 cc43e075a..9347b3de8 100644 --- a/Riot/Categories/MXRoom+Riot.m +++ b/Riot/Categories/MXRoom+Riot.m @@ -324,6 +324,37 @@ } } +- (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 224aa2b0de9e3f6516c9987b6e75a951a66cb8db 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 3c49b1ef0..d08ef1346 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 @@ 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 @@ 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 05318f1a7ec4d7f3ecbef50e5ec9da3702a53d18 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 32a7ff69c..368b5386f 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 f55ce88e9..050cb95d6 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 @@ }]; [self refreshContactPresence]; + [self refreshContactBadgeImage]; } else { @@ -234,6 +236,39 @@ 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 2209926d9..f27b9c893 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 8e0f682f122f838d5e6f54512f1fc1eb470179c9 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 e2b0b20de..1f5c9cd92 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 54778223b2a0a80337a59383e260fd6a7504c339 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 ce2bb7aaa..a9e8845e4 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 fcfc83e273a07c34c4af0a730b6f6e79c7252f1d 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 8a7808977..ba840c89e 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -271,7 +271,7 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe // 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 4bfd93e1a5ba0816a17155260acb666d7105290b 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 1f5c9cd92..871e6ef86 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 ba840c89e..9ae7fe0ae 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -2125,9 +2125,13 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe { 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 e87058dea978926ec1e7baf958e3808026746e4f 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 50d20cbb4..117d3f6d2 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 62f40364829ea8133c3d14953b846d0fea6786da 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 | 166 ++++++++++-------- 3 files changed, 103 insertions(+), 81 deletions(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 67a3384ef..2a3cdad8a 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 ab61ffc81..895d43207 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 cae168391..269188868 100644 --- a/Riot/Modules/Settings/Security/SecurityViewController.m +++ b/Riot/Modules/Settings/Security/SecurityViewController.m @@ -34,25 +34,30 @@ enum { SECTION_CRYPTO_SESSIONS, SECTION_KEYBACKUP, + SECTION_CROSSSIGNING, + SECTION_CRYPTOGRAPHY, SECTION_ADVANCED, - SECTION_DEBUG, // TODO: To remove SECTION_COUNT }; +enum { + CROSSSIGNING_INFO, + CROSSSIGNING_BOOTSTRAP, + CROSSSIGNING_COUNT +}; + +enum { + CRYPTOGRAPHY_INFO, + CRYPTOGRAPHY_EXPORT, // TODO: To move to SECTION_KEYBACKUP + CRYPTOGRAPHY_COUNT +}; + enum { ADVANCED_BLACKLIST_UNVERIFIED_DEVICES, ADVANCED_BLACKLIST_UNVERIFIED_DEVICES_DESCRIPTION, - ADVANCED_EXPORT, // TODO: To move to SECTION_KEYBACKUP ADVANCED_COUNT }; -enum { - DEBUG_CRYPTO_INFO, - DEBUG_CROSSSIGNING_INFO, - DEBUG_CROSSSIGNING_BOOTSTRAP, - DEBUG_COUNT -}; - @interface SecurityViewController () < SettingsKeyBackupTableViewSectionDelegate, @@ -339,12 +344,6 @@ UIDocumentInteractionControllerDelegate> // 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 @@ UIDocumentInteractionControllerDelegate> 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 @@ UIDocumentInteractionControllerDelegate> 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,31 +612,59 @@ UIDocumentInteractionControllerDelegate> } } } - 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: + case CROSSSIGNING_INFO: { - 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; + MXKTableViewCellWithTextView *cryptoCell = [self textViewCellForTableView:tableView atIndexPath:indexPath]; + cryptoCell.mxkTextView.attributedText = [self crossSigningStatus]; + cell = cryptoCell; break; } - case ADVANCED_BLACKLIST_UNVERIFIED_DEVICES_DESCRIPTION: + case CROSSSIGNING_BOOTSTRAP: { - cell = [self descriptionCellForTableView:tableView - withText:NSLocalizedStringFromTable(@"security_settings_blacklist_unverified_devices_description", @"Vector", nil) ]; - + 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; } - case ADVANCED_EXPORT: + } + } + else if (section == SECTION_CRYPTOGRAPHY) + { + switch (row) + { + case CRYPTOGRAPHY_INFO: + { + MXKTableViewCellWithTextView *cryptoCell = [self textViewCellForTableView:tableView atIndexPath:indexPath]; + cryptoCell.mxkTextView.attributedText = [self cryptographyInformation]; + cell = cryptoCell; + break; + } + case CRYPTOGRAPHY_EXPORT: { MXKTableViewCellWithButton *exportKeysBtnCell = [tableView dequeueReusableCellWithIdentifier:[MXKTableViewCellWithButton defaultReuseIdentifier]]; if (!exportKeysBtnCell) @@ -646,71 +676,49 @@ UIDocumentInteractionControllerDelegate> 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(exportEncryptionKeys:) forControlEvents:UIControlEventTouchUpInside]; exportKeysBtnCell.mxkButton.accessibilityIdentifier = nil; - + cell = exportKeysBtnCell; break; } } } - else if (section == SECTION_KEYBACKUP) - { - cell = [keyBackupSection cellForRowAtRow:row]; - } - else if (section == SECTION_DEBUG) + else if (section == SECTION_ADVANCED) { switch (row) { - case DEBUG_CRYPTO_INFO: + case ADVANCED_BLACKLIST_UNVERIFIED_DEVICES: { - MXKTableViewCellWithTextView *cryptoCell = [self textViewCellForTableView:tableView atIndexPath:indexPath]; - cryptoCell.mxkTextView.attributedText = [self cryptographyInformation]; - cell = cryptoCell; + 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 DEBUG_CROSSSIGNING_INFO: + case ADVANCED_BLACKLIST_UNVERIFIED_DEVICES_DESCRIPTION: { - 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; + cell = [self descriptionCellForTableView:tableView + withText:NSLocalizedStringFromTable(@"security_settings_blacklist_unverified_devices_description", @"Vector", nil) ]; + break; } } } - + return cell; } @@ -722,10 +730,12 @@ UIDocumentInteractionControllerDelegate> 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 c3e3a557d81301d485563c1b4eb32c04ec11f789 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 000000000..93cacaafc --- /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 ac2a7bf05288959ed5430a7373896b509183377d 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 000000000..721e9f596 --- /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 a303af35fdef1946a1aae962ab87ab9cd5ec30cd 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 6ab0a6eee..749e9abe5 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 38747c45138f46cd0d199d9fa2e21f75bc90ce37 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 000000000..a46a0a9f2 --- /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 2c078e0a01f5373aae5fcf19d6ac5d78a9ae5895 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 000000000..f3ad32c05 --- /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 a66030d1ede151d99dfa2cc6d9cfeb348807d51c 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 000000000..8a084200a --- /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 000000000..47f032ef0 --- /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 d4e66d34d64c40c300c8b270d4c441f1b16c8ed6 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 000000000..d21aeca7b --- /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 f31988bd7..7f4d0c7e7 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 f6dc514053a15bd4fc152af15a6ec58a3aab1744 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 67a3384ef..b8a72492c 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 ab61ffc81..c8c9d9b57 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 9d1415219fc1d09330f22ae1176c8ffebe3ce044 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 66ef12a4b..d5db2dc65 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 000000000..73ef5980a --- /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 000000000..43b575859 --- /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 a5d87b7078447b9ddb5e27eb53765722979f835b 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 000000000..344c1be44 --- /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 000000000..50cb3a380 --- /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 000000000..617dd202c --- /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 000000000..b4cfc8855 --- /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 000000000..561de5277 --- /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 000000000..df48f6e91 --- /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 47f032ef0..c3bb92fde 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 55587b7a64119351f3fe6120c7697601d6b1b8ce 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 000000000..4df2095f3 --- /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 279ca49a449ccdfe56569b305f090f69296c0d86 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 461e2fd43..3c1ad6289 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 057059ab4..7caeea702 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 693164375..79a8ecf48 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 c553b4317..89ee68523 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 8135e3ccc500bba81554247205ff7b7006c914be 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 ffc2ce99f..397a38914 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 2f59b1973..0d9288bb7 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 b893fe752..9b9c1b6ff 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 ce8feb50a..4768288d1 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 013719e533457246a7a48f12f6aff91f9c749e2d 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 73ef5980a..7aa50203c 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 332d11ca8903fba1e80cc0a1ff69394dcf5302cd 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 6944e0900..270bd94cb 100644 --- a/Riot/Modules/Room/Members/RoomParticipantsViewController.m +++ b/Riot/Modules/Room/Members/RoomParticipantsViewController.m @@ -1127,20 +1127,28 @@ { 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 64663310ccd70bc49bf4a478c85cd19d09671020 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 fb9e86b8b..b4c48231b 100644 --- a/Riot/Modules/Communities/Members/GroupParticipantsViewController.m +++ b/Riot/Modules/Communities/Members/GroupParticipantsViewController.m @@ -826,12 +826,15 @@ { [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 824d7519806003e74730e7d6034d7fcef8a15453 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 9057c3ad4..0b214b04e 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m @@ -97,6 +97,8 @@ @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 @@ 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 @@ { 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; + + 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 if (powerLevel >= RoomPowerLevelModerator) + + NSString *powerLevelText; + + if (powerLevelTextFormat) { - self->memberTitleView.memberBadge.image = [UIImage imageNamed:@"mod_icon"]; - self->memberTitleView.memberBadge.hidden = NO; - } - else - { - 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 b723cb4f4..999ddf2fc 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 7dbdb7f413e4d2458a89d5423f697e0dd67001b1 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 e81d3faf7..009b77e22 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 b86be3ba7..50eaf408d 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 1a4c385eb7c9044d46d5ece9bb5801559e9f74c9 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 537c5168f..453addbd9 100644 --- a/Riot/Modules/Room/Views/Title/Expanded/ExpandedRoomTitleView.xib +++ b/Riot/Modules/Room/Views/Title/Expanded/ExpandedRoomTitleView.xib @@ -28,19 +28,19 @@ - + + - - + - + From 203bc41865fd248aeaf09c0db8f0418f3b2931a7 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 ea0162bc9..45366f0ff 100644 --- a/Riot/Modules/Home/Views/RoomCollectionViewCell.xib +++ b/Riot/Modules/Home/Views/RoomCollectionViewCell.xib @@ -53,11 +53,11 @@ - From de272fcd65a24a3578f3663dbc162475d11a8fab 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 720ed34f4..113b64f94 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 3aeb43764..651e5acd9 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 cd754d8d4..4fa5cc5dc 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 8cd322e45..61fb2f39f 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 b8869aafd..2a00f40ae 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 8dca49e5c..11203ab9d 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 cc23f78f3..7a74a9fec 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 9b716cddf..77781f0f5 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 fd76d45b1..b1ce5cfc2 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 815fd8196..f4b31b839 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 c9d6a01fb..2955714c9 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 dae52e6a6d7f324db89c23365389a27d72c81782 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 a6abf9587..cf3abe209 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 000000000..674989ffc --- /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 c13439835a3f727ddee67271aa2f87e006e6eaaa 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 1a634e8b6..500d75a79 100644 --- a/Riot/Modules/Contacts/Views/ContactTableViewCell.m +++ b/Riot/Modules/Contacts/Views/ContactTableViewCell.m @@ -247,34 +247,7 @@ 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 8bd307ecf..34ccbb861 100644 --- a/Riot/Modules/Home/Views/RoomCollectionViewCell.m +++ b/Riot/Modules/Home/Views/RoomCollectionViewCell.m @@ -162,7 +162,7 @@ static const CGFloat kDirectRoomBorderWidth = 3.0; 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 @@ static const CGFloat kDirectRoomBorderWidth = 3.0; 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 0b214b04e..d323154fb 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m @@ -440,7 +440,7 @@ 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 @@ [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 @@ 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 b4f54dc8db595fc337ffa73aae8087ad0f093f9f 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 23b198f5e..52fe01acf 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 898103702..8e7329564 100644 --- a/Riot/Modules/Room/Views/Title/RoomTitleView.m +++ b/Riot/Modules/Room/Views/Title/RoomTitleView.m @@ -37,6 +37,8 @@ { [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 92c0c947c..6b4b0722d 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 955c399fd3b07a69b60860ac6aee764721fa4c5d 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 cb65bd172..781970e12 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -1252,6 +1252,13 @@ [self cancelEventSelection]; } +- (void)setRoomTitleViewClass:(Class)roomTitleViewClass +{ + [super setRoomTitleViewClass:roomTitleViewClass]; + + [self updateTitleViewEncryptionDecoration]; +} + - (void)destroy { rightBarButtonItems = nil; @@ -1630,34 +1637,13 @@ - (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)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 @@ { [self updateInputToolbarEncryptionDecoration]; [self updateExpandedHeaderEncryptionDecoration]; + [self updateTitleViewEncryptionDecoration]; } #pragma mark - Segues From 024e45467fd63c1d8e9966f3028023a0fb66aeca 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 4538e7c43..a41e3cfed 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 641025fac4ef6d113d8c2017cf711f6ea9eb9bf7 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 86c886df8..2501eb67c 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 2c84b2de8..0e536c1fd 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 fc80cf7ba..69b27dd66 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 b9f23f4fa..13e0da4c0 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 3912c1715..4bff0e7a8 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 000000000..1ef8aab16 --- /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 000000000..2512039e0 --- /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 000000000..b50aa9667 --- /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 000000000..5ad5a61bf --- /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 000000000..1c8ba341c --- /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 000000000..1aaf96e62 --- /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 000000000..6e697b612 --- /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 000000000..ed030362f --- /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 000000000..fcfe3afa2 --- /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 8d126897d..dd5a834da 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 14f707c2e523634732078821e2d2c26b0a4bf631 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 4bff0e7a8..88ff6d375 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 2512039e0..f5b2d3831 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 b50aa9667..aab042ba5 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 6e697b612..5fd6e1440 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 ed030362f..1188e0560 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 7ee6dea00a32f066fd1214841cb5902ed94bfca0 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 fcfe3afa2..bdd417853 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 38e5a1220a91f8a729e772e511597f5fa1b93ca7 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 5fd6e1440..408a404df 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 aad459cf9e0bb2977a35ae91666f254da6ece824 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 f3dc507a9..5b21ad861 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 00bf54502d05285d024439280565b59e50590713 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 bb21728b0..881604cb0 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -4844,6 +4844,16 @@ NSString *const AppDelegateDidValidateEmailNotificationClientSecretKey = @"AppDe } - (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 9147ff8af..22d0108b4 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.m +++ b/Riot/Modules/Authentication/AuthenticationViewController.m @@ -1347,7 +1347,13 @@ #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 df568f8ce..1043f3616 100644 --- a/Riot/Modules/EncryptionInfo/EncryptionInfoView.m +++ b/Riot/Modules/EncryptionInfo/EncryptionInfoView.m @@ -71,11 +71,21 @@ } } -- (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 54dc6d94f..65cebca08 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 2786a7bcf..e6bd0a697 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 5e84c151f..901d6b11d 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 7e49fce01..a3cc53577 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 d323154fb..2736c15be 100644 --- a/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m +++ b/Riot/Modules/Room/Members/Detail/RoomMemberDetailsViewController.m @@ -1359,6 +1359,16 @@ #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 168662b4e..12e04b1c6 100644 --- a/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.m +++ b/Riot/Modules/RoomKeyRequest/RoomKeyRequestViewController.m @@ -146,20 +146,30 @@ #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 @@ [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 58a89c0d9..bac4e6be6 100644 --- a/Riot/Modules/UserDevices/UsersDevicesViewController.m +++ b/Riot/Modules/UserDevices/UsersDevicesViewController.m @@ -246,17 +246,27 @@ - (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 0a3136d3530102348ee1379e6764b144f334feb1 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 22d0108b4..1155c89a5 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.m +++ b/Riot/Modules/Authentication/AuthenticationViewController.m @@ -1185,6 +1185,11 @@ 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 @@ } else { + [session.crypto setOutgoingKeyRequestsEnabled:YES onComplete:nil]; [self dismiss]; } @@ -1354,6 +1360,8 @@ - (void)keyVerificationCoordinatorBridgePresenterDelegateDidCancel:(KeyVerificationCoordinatorBridgePresenter * _Nonnull)coordinatorBridgePresenter { + // Set outgoing key requests back + [coordinatorBridgePresenter.session.crypto setOutgoingKeyRequestsEnabled:YES onComplete:nil]; [self dismiss]; } From ddc21bf6121e7f5ace32448695d98028bcf97e3b 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 975146130..b5917fc84 100644 --- a/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.m +++ b/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.m @@ -86,15 +86,14 @@ static CGFloat const kTextFontSize = 15.0; 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 @@ static CGFloat const kTextFontSize = 15.0; [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 687d1ef05..13f41d437 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 000000000..ec3133946 --- /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 000000000..7407a0e2f --- /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 000000000..03d843560 --- /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 000000000..c91b35a9f --- /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 9c82869754cb78ae6fec858474f92e82a2844a89 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 344c1be44..1002d8cd3 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 50cb3a380..faa3ec9ee 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 087c0fe0d..8462e051b 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 561de5277..4be0568d5 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 3628e45a6253bf8acb329da9b3215ec8a69d72c4 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 65cebca08..aaf890f89 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 c788a43cd07fd5a732925a6494ac3f65b850309c 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 5b21ad861..87739abb7 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 f3e36eae9..074346fe1 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 6d4c865f2d7f62681e1443eb5f2f4feb9ad7d48d 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 611ec4f21..89ca47a43 100644 --- a/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewController.storyboard +++ b/Riot/Modules/KeyVerification/Common/Verify/SAS/KeyVerificationVerifyBySASViewController.storyboard @@ -91,7 +91,7 @@ - + From 5453daa67f6223fb9e600bb58413a3b65d5acec9 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 c3e9a6782..26359b208 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 37ff450d72cc87f0d1241bc3e135e807f227d422 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 89ca47a43..afd2968eb 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 99826c86354c19018395a6b80a03a916c8bc38ff 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 3ab25b73b..37848b595 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 4e2953be7f8c3c46180a1bed9655c3f2214da9f6 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 26359b208..87e646278 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 87739abb7..68c5cf206 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 074346fe1..5ec7bb55f 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