From 15d4231df6b014a7fcd63fb6af8774187b51ffc4 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 20 Dec 2019 10:30:59 +0100 Subject: [PATCH 01/98] 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 02/98] 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 25bb439aff3ad6461228f7077bc13d5e74a74113 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 20 Dec 2019 10:32:33 +0100 Subject: [PATCH 03/98] 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 4dcc6fb861d1473431911cef72537bb293430c15 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 20 Dec 2019 10:35:04 +0100 Subject: [PATCH 04/98] 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 fc77865f844adc55837f6365b1f3517f957cc696 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 20 Dec 2019 10:37:53 +0100 Subject: [PATCH 05/98] 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 d4fd68e40551fae87a41b1de7341ed353c4d6ce8 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 20 Dec 2019 10:39:22 +0100 Subject: [PATCH 06/98] 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 48db9c37a4f470d8748c74864bd0a9d0f0b0b9a2 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 20 Dec 2019 10:42:28 +0100 Subject: [PATCH 07/98] 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 2fba33e598d78f6110c3c9f43f6ca8a67ed5409d Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 20 Dec 2019 10:43:07 +0100 Subject: [PATCH 08/98] 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 5cfe5833ce2b3a8cd33ffc73ab2af1add3a13050 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 20 Dec 2019 10:44:01 +0100 Subject: [PATCH 09/98] 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 1d52a55d8a1a6889d5539df06689d663a5a89e9b Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Thu, 26 Dec 2019 23:24:09 +0100 Subject: [PATCH 10/98] 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 c8a68fb2d8710d767b449e26a8ff2d01f603b3de Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Jan 2020 19:21:32 +0100 Subject: [PATCH 11/98] 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 d9408dda330de74c0e133e127b9d709152bd8723 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Jan 2020 19:46:02 +0100 Subject: [PATCH 12/98] 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 a47522b7c51b670883a625d34019019cdf81318d Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Jan 2020 19:55:49 +0100 Subject: [PATCH 13/98] 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 194e314afe721d748735114c68b9a723a8f74b05 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Jan 2020 19:56:56 +0100 Subject: [PATCH 14/98] 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 05d0ab7ff4077780e8f8098ec41537f02e634e56 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Jan 2020 19:58:16 +0100 Subject: [PATCH 15/98] 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 6fe6067529c104d6bf7eea780145dbb5af08db4c Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Jan 2020 20:02:34 +0100 Subject: [PATCH 16/98] 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 28bd41f19711aac386cfa1e30dd619cc9c871972 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Jan 2020 20:10:13 +0100 Subject: [PATCH 17/98] 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 148c0686fa7aa7872f435f1a3f43dc6c458e8894 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Jan 2020 20:14:17 +0100 Subject: [PATCH 18/98] 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 d290938fb10d9d9b6b5ee1bfc0938d75db020220 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Jan 2020 20:15:28 +0100 Subject: [PATCH 19/98] 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 3ea3f1f84728dd4b8ab9f4e77a446580dcc4e4fe Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Jan 2020 20:23:36 +0100 Subject: [PATCH 20/98] 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 0ad759c18e9197ddc7d87d43efeaa3196c84d02a Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Jan 2020 21:47:16 +0100 Subject: [PATCH 21/98] 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 972a1174c891a561a0134567bb219bb1d408301f Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Jan 2020 21:51:05 +0100 Subject: [PATCH 22/98] 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 f5ca4d4bf80969738a889b0b48037df813a7fd9a Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 14 Jan 2020 21:52:02 +0100 Subject: [PATCH 23/98] 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 c659e87a3236616336fa8b8d808fa6d354074aae Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 15 Jan 2020 08:38:35 +0100 Subject: [PATCH 24/98] 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 b41343f3f6f3f8aab755221423df419ac29e2fb8 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 15 Jan 2020 10:50:36 +0100 Subject: [PATCH 25/98] 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 dd3740aad658118831f1c1466e8c82558b986d57 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 15 Jan 2020 18:18:22 +0100 Subject: [PATCH 26/98] 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 cd7b31fb8fb40a0bb0624123ea8512a7cc02f131 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 15 Jan 2020 18:18:32 +0100 Subject: [PATCH 27/98] 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 2025d09e9c228cae78af05a7871a04db9a2677c4 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 15 Jan 2020 18:18:46 +0100 Subject: [PATCH 28/98] 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 3d7c4689366a88371cb01dbd679f45b3dcbbe020 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 16 Jan 2020 09:19:39 +0100 Subject: [PATCH 29/98] 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 c8a1d8fdef2499123ca8790f838a9d4ecfcf01aa Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 16 Jan 2020 11:02:46 +0100 Subject: [PATCH 30/98] 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 1d57c82f2f309c16c17cc6cdc6c765f1e106f250 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 16 Jan 2020 12:58:14 +0100 Subject: [PATCH 31/98] 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 9f562b8bf78bd177e563d54d4c1ae25bf07ee982 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 17 Jan 2020 10:52:26 +0100 Subject: [PATCH 32/98] 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 f73bb2593959aa6c167f981b3f8ab91f7acebbe1 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 17 Jan 2020 10:54:18 +0100 Subject: [PATCH 33/98] 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 1cca4fa2306cd966ee14202bf78e3939db6c9684 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 17 Jan 2020 15:52:49 +0100 Subject: [PATCH 34/98] 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 927a909f21ce016c2b1992f164fe538e2400f147 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 22 Jan 2020 16:21:47 +0100 Subject: [PATCH 35/98] 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 39e841963eaa8385cc33889f2fb8cf70586b8335 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 22 Jan 2020 16:23:39 +0100 Subject: [PATCH 36/98] 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 69a04c1af82bf62d838465d39b5b9ed18d669235 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 22 Jan 2020 16:25:39 +0100 Subject: [PATCH 37/98] 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 2ab9a4ae83516b121bf655d3765e172141706d49 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 22 Jan 2020 16:27:04 +0100 Subject: [PATCH 38/98] 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 90da9b38687ea020d669cffb1a8f0340ba72d26d Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Wed, 22 Jan 2020 16:45:33 +0100 Subject: [PATCH 39/98] 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 d2e93bb9aa1cbef7bd096dc61e47fd4dc8f450dc Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 23 Jan 2020 16:37:23 +0100 Subject: [PATCH 40/98] 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 a7df696f5b0ac6ce7d44154e21d855643de43f73 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 23 Jan 2020 16:39:33 +0100 Subject: [PATCH 41/98] 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 5480e913adbcd0abc5fdb3dc0370b269651e46a7 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 24 Jan 2020 17:13:08 +0100 Subject: [PATCH 42/98] 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 54a9ab88b8d8a23bdc8bf4e1b0d0563285fbca6d Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 27 Jan 2020 17:30:09 +0100 Subject: [PATCH 43/98] 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 7b24865be47e40cc1e93726863e8012f1f543988 Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 28 Jan 2020 14:33:56 +0100 Subject: [PATCH 44/98] 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 12ec147b62c848906293d901fc35a012963a57b2 Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 28 Jan 2020 15:10:00 +0100 Subject: [PATCH 45/98] 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 589c1ca3b641472e8a19c30f4188666a17aea192 Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 28 Jan 2020 15:53:09 +0100 Subject: [PATCH 46/98] 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 a669f35fc66db495ca602b41003b0d6b940525eb Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 28 Jan 2020 16:51:03 +0100 Subject: [PATCH 47/98] 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 c75a9e54c1277e046ecbcc6df35b75e3cef6c516 Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 28 Jan 2020 18:18:19 +0100 Subject: [PATCH 48/98] 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 ae3674b8ed388fd9bebbf6216d60dc563d3fcae5 Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 28 Jan 2020 21:09:51 +0100 Subject: [PATCH 49/98] 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 133e26af36041dd893cebb8001f464bf5f4f439d Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 28 Jan 2020 22:05:11 +0100 Subject: [PATCH 50/98] 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 9f9f74ec24d11ee67ec04850f062d94e2ff3d73b Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 28 Jan 2020 22:36:50 +0100 Subject: [PATCH 51/98] 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 7a2d3b4e07960ce57753fe719ec06eb6fc8df8d6 Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 28 Jan 2020 23:27:45 +0100 Subject: [PATCH 52/98] 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 6f3ae21797b6f810d36448dba68fb4e36d80928e Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 29 Jan 2020 08:19:48 +0100 Subject: [PATCH 53/98] 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 89a975481f149cf651dd96a7ae47ff3d8c4f532a Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 29 Jan 2020 09:32:50 +0100 Subject: [PATCH 54/98] 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 d41fca982d2489792a6f22e01c6d990775492af8 Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 29 Jan 2020 15:56:06 +0100 Subject: [PATCH 55/98] 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 f136c714c6bac4ad8c6eb7a119bd6c25398af827 Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 29 Jan 2020 17:56:24 +0100 Subject: [PATCH 56/98] 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 0267098edec650dce45d4fc5b8d5038a46d93032 Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 29 Jan 2020 22:00:05 +0100 Subject: [PATCH 57/98] 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 27149e06ef25d32e1c45db5d4320268617c55615 Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 29 Jan 2020 23:17:57 +0100 Subject: [PATCH 58/98] 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 af0a45ef54ba16750b67dee1864d11b7521defb7 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 30 Jan 2020 06:52:20 +0100 Subject: [PATCH 59/98] 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 76541580a017896b9c838d40a183274a743395eb Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 30 Jan 2020 07:01:01 +0100 Subject: [PATCH 60/98] 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 30359d42e059aa8c8f9c8efaf703508194a99819 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 30 Jan 2020 07:14:35 +0100 Subject: [PATCH 61/98] 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 7daa6c917a02aa486d905fbb02fcc11158dfe415 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 30 Jan 2020 13:14:17 +0100 Subject: [PATCH 62/98] 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 3219416f338de49c918df1377ea20348e40eea0b Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 30 Jan 2020 13:24:13 +0100 Subject: [PATCH 63/98] 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 750be94854de9a27cdc1c210019b2d131b3025af Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 30 Jan 2020 16:08:58 +0100 Subject: [PATCH 64/98] 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 82c1578aaaa5b54b1ee0d152351a6bca018734bc Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 30 Jan 2020 17:07:53 +0100 Subject: [PATCH 65/98] 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 6819d3d00529643c533c5df8c11e6e557a1cc19a Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 30 Jan 2020 17:38:33 +0100 Subject: [PATCH 66/98] 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 46efdd164ad1a73720dcce20cd0466f9dc6d1a45 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 30 Jan 2020 17:41:51 +0100 Subject: [PATCH 67/98] 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 3b005d282d24d136d6f74acd5ae1002ac39a7b0a Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 30 Jan 2020 17:42:54 +0100 Subject: [PATCH 68/98] 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 23a23445fa5f2cbae705e78e07c4b626f8a439a5 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 30 Jan 2020 17:48:22 +0100 Subject: [PATCH 69/98] 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 ad5889bf2f43d53f3debc021ea5e276852d38d14 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 30 Jan 2020 17:54:03 +0100 Subject: [PATCH 70/98] 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 4e6f5a0800b1ffc86b8bdd9753b0281518935e84 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 30 Jan 2020 17:55:36 +0100 Subject: [PATCH 71/98] 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 6e25793c0a11e8cb55ce88a5f4c7472fec02c6f7 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 30 Jan 2020 17:57:09 +0100 Subject: [PATCH 72/98] 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 86036867d63d7c3c532d80589d11ec007f66210b Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 30 Jan 2020 17:59:42 +0100 Subject: [PATCH 73/98] 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 0d23b2d46132caf3b26a259a9f8bb3b674a21d5c Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 30 Jan 2020 18:05:41 +0100 Subject: [PATCH 74/98] 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 76bcb0256eff3923e583e78efca1df62c60f52e5 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 30 Jan 2020 18:05:55 +0100 Subject: [PATCH 75/98] 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 178d6e5b974b6d505169dfecb4c3128059ad5b72 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 30 Jan 2020 18:13:49 +0100 Subject: [PATCH 76/98] 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 01612d735a0438359f6aa19eeb87658950b4312c Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 30 Jan 2020 20:57:51 +0100 Subject: [PATCH 77/98] 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 d37e23edfc72e887eae9e12c1e967da9542a75ac Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 30 Jan 2020 20:58:12 +0100 Subject: [PATCH 78/98] 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 81466c5417d40388eeb7fe13444739fb3d89019b Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 30 Jan 2020 21:37:25 +0100 Subject: [PATCH 79/98] 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 c70452661a4ac1d8a93c88d4412f527d7a7345f1 Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 31 Jan 2020 14:59:54 +0100 Subject: [PATCH 80/98] 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 b5e20087cb026211b8220a98a67498d3cc0cbcfe Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 31 Jan 2020 15:02:02 +0100 Subject: [PATCH 81/98] 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 5ad1596b8f9ee0de6b59d462a4d1eb9bb1d87432 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 31 Jan 2020 15:03:44 +0100 Subject: [PATCH 82/98] 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 f008ae40f10baec99da39ea18b0b49bf14413d3c Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 31 Jan 2020 15:07:30 +0100 Subject: [PATCH 83/98] 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 0695ed215b2f956213307cfbaafcb67499a4f75b Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 31 Jan 2020 15:09:19 +0100 Subject: [PATCH 84/98] 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 fb97b498c615dfe25831bede5514aab19871a36b Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 31 Jan 2020 16:20:28 +0100 Subject: [PATCH 85/98] 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 beb185dc026bbf59ddcb3af72c432bbae1f42fa4 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 31 Jan 2020 17:46:03 +0100 Subject: [PATCH 86/98] 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 3ff40c59a2a837adf83715d01b1243e2772f0655 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 31 Jan 2020 17:46:16 +0100 Subject: [PATCH 87/98] 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 09c77e61425a3eb733389d5223de1597cd109d8c Mon Sep 17 00:00:00 2001 From: manuroe Date: Sat, 1 Feb 2020 17:25:23 +0100 Subject: [PATCH 88/98] 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 34a3de9f0be912c08129285d169cf6a24361eaae Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Mon, 3 Feb 2020 17:48:25 +0100 Subject: [PATCH 89/98] 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 72da7b1f2e774d30ad75a8b0878f81f37a81b47f Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Mon, 3 Feb 2020 17:50:52 +0100 Subject: [PATCH 90/98] 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 ddfe4554f95698be5cf78b0e2615c8690b15aa20 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Mon, 3 Feb 2020 17:52:39 +0100 Subject: [PATCH 91/98] 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 bd131f12a9cb42797e805979f148bb3e4a771a66 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Mon, 3 Feb 2020 18:07:53 +0100 Subject: [PATCH 92/98] 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 22b9b6df215ad8d9ab470d91cfb456eae5987951 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Mon, 3 Feb 2020 18:59:25 +0100 Subject: [PATCH 93/98] 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 e05f3ae6afcbad0fed14169d5521d713fffbb812 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Mon, 3 Feb 2020 19:00:33 +0100 Subject: [PATCH 94/98] 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 ebf089115f1f7fc9a82a407b2808ed3a92c9613a Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Mon, 3 Feb 2020 19:01:47 +0100 Subject: [PATCH 95/98] 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 @@ + + + + + + +