From 0433d8cf81013ba854a9748e9717a157bee12413 Mon Sep 17 00:00:00 2001 From: Krille Fear Date: Sun, 10 Oct 2021 11:40:08 +0200 Subject: [PATCH] feat: Nicer chat backup design --- assets/backup.png | Bin 0 -> 6906 bytes assets/l10n/intl_en.arb | 4 + lib/pages/bootstrap_dialog.dart | 248 ++++++++++++++++------------- lib/widgets/encryption_button.dart | 2 +- 4 files changed, 145 insertions(+), 109 deletions(-) create mode 100644 assets/backup.png diff --git a/assets/backup.png b/assets/backup.png new file mode 100644 index 0000000000000000000000000000000000000000..b17a7614ac7ba5f2eb40e2823a0d8d722972dfbf GIT binary patch literal 6906 zcma)BR&p9(^esgBd#A#_L1Mw*F0000`RYgJPNrwGLI9N}-Ps(!iN#MGv7<&Q$ z_%Hq=G(g@bFaSVVqpBdQ=lAK@4A)4{u&Kc2*H~cs7+iRGET5V2r~B4398R2y3Y_*P zYYYru3>*F}0tXU>MEr`MVA5^&1WH9kTVW;bS1%ai>BZTtkn}bpZgWYb~u1URaB%+@e$B=4zJa?{ts8yEX+y$kK?0Uxrb#SZh z&D)W+uIh0MJ9L|eWX~zDi21#16p4Npv(x-F^}zGX*IvmE*qkjoA$5fR26zO$+*`tJ zZ6s3k-mhW5g+#uGkNBL4ch4>pd59*d6l^?RUA#O$V1m7F<3||R!4xO6o<5FvL(qX* zQJ^DubGfUUttio;+?NwQxDl!m!BBceS-0v`sYfpwq(S*+d4AXglR4Dc@B%iQxF5rf zpFA=jCuN$ugY80$Lg{vIgb%sUVlXF23)1KIC6DfzS|@G@$;pr*DS#W%U~Na!Zr$6i zReqOmJZ)~9TE6P-Ki%mWy)Sa{N6pkE898l*BVxSg$JY#;P6WLsFxKo z9oA~S8V|F%S-BmFyMMU^V!O9TNcF}<5A`QOcO56J{guLWbWHC5pjv9ng^9T)`tJ8n zM7?yV=jZp**Q8y1y1?Phd`A`?`eMT_)$M$asb1)dfuDfsNPoW5qIM9s^gyt-`S{i- ze?&9j>|@*2)gT&f4l~)kF6>4bMylHns3C*z%WajRQ|ELl`N*-40-_K%t1}$bpSGk7 ziwqkKR{jNS6Xkq2d(CEb{i{4;QF^%+{GK@@*e3S$H8gUYcsB`pO?i&GUSx5>$WIrY zv|)?pu7Y>iMzncfE1_+p%*AkZvlf2QV?p_wTH2%WGi|?qfv32_ z!bn`YdLHnJdjch`0n9bG=TO<8SkUd~9a&Yy=CPUn_`EX5<@q%#G(A6tu3fH|fKgGx zfHA;_l!@Z?N&+UM&p&oY@G%K#djgl|E$Xq?0AwNS8S9pSc%iUajzC?JFxtuoKpeKa z@t^XEI7$kah?PeLsjs2eG^4}awIzM5~vWvrI5WPq$ zQ>U6ypIC)6&9xIona8#QmRk)+JrgdRODV-3hmfGau6hZT0eWg0^LjYg!`W{$9uos!){6T_u(^JS;=tF( zd|;kJQJ24V09db%&TcKM?wt))k5B&9( zwgU+z07OOS^9|VZDg}FKVQ2D%3URjETe%M`R=J7jCGpF@&6ekF>!Y9~+(~M>>n zOY+5NDq_!d3_eA+yIpm>C*-p<7hU8ITD?N?FvdB?g8ocEn{B{7_;yuBicnfNwF;a2 zl#ar=C44uN;phmfVJv9t1|;!j4D($*n~wD9CCfNIR3$}7QNe}R(ImTz7_AZ>EXNY~ z$3KmQ7o?|TXL+Ti$La!jWuBaJo6!E3i!=Z$_`}YW;>Xz|2U4MN>Hk%#Se%T;GxzG) zjbR6Q@nFHydzySuoRIdqo!-pC6%Smh(`>$pZN{|1q)B~lL{6e5blzY&ZqvPPEdD8{ z?^-O++t>Fsy}8xO!A?1#$4@5K7H|-<=m^YWqgdjJz)Gh1jh5AnZ@D`5vf(L^m@OLfe?1$@DI7Kr8Y9h&bYWriRc8$#D0B?h&3%8iMBmwIe>?{u z6Wr>B6w#JJHp6)zk;(o%9@1>;@3Utr!4e*W@R06N4k)|27b1E2Ymb>yhdF0l9h zo`6Xtb2RIpPg?xSxXXVI!Jw3>(&~kP5b*>tgFnE8x(a2taogan@A zv63@TSrvhrZ<7iHpM9+SYZ%JJ3$3cKy~{R$b5>Qj_!wRbU{dzH{uwp()6jp*eB-N9 zVi?W=_(apDuME6o86Hw;GgFCGXIup7jn!uA~o%ov0e7*r-nsq)aegDuVy3* z^4_nQk87f?%89agzZpL9-GYzYkM$FA!G@VZ`NTw#1j8oO9)8IRk|Z?pxQy0AXRo&% zpDLZOz|UEXeF)CzD>M5|kotrx9NWX1`ny;S5;^xJwH^zTbYAzmKX1Z%|@tVm7cVI47aw=&lgoN_!)P~El57}%0+b1 zh=OJ)*156hOz5ac@N6MWZ(`<}$*SQ3LPR06@+m9;63x!E^AEOx_Ydzpff7YORR|u|8TeC%^EM|S+QG{ zdYdb80ozPp`2Kx6+0k$iPdu@w7FSv3UT`e4or@aznUhn54~)HTD2|Ru-&@*f?Y5Ug57y&G z8nr5*i{$BHT(*MWVheBoYkN-He8`*kTZM_8J4)JSM_X0}sU|0p+Cp6wgi;OBzAYH? zM^TEFUkc7dFRA>~HGQA*)pjv-IIt`(?v(Dw~BeSG?RK&&Q-iYZ~;mDHgF1cJDMzoTa=6b23Rk`gB)LVaF-?r4(CvfxZS zjT%EM!#90C6cbeRLlEdJ$NGhFq!h$UBDlx1;Ttmt!pkBJYpZ_NfySLso#p@twEk;T zhY#Fb*2+)$nP*#1MA44Nji^z+N_I95~cQKa*5F!t8&24OqE0*RpZ0-ACKL;xP=V!F|+I zGI%Pva0!P#PmrY*ri~*WkKL;T#Ioq(E~2;2)#FqS{#u7( z;@^BBV9GJZdL=cI9Kyx;m}>SH57kVAMa0a4!Hq9ocec!@`d$wA)Slv6I&VYQG71fz zpQSlI&|fFpa^zXjA#Z}3AE`HZeo4zBg)k8s3fTsh-FXQ)~D zoJ=gp$WL!LsF*|)sK=sd)SZN`_R3cxdzG;)JDjUD3P6Yo=B)Ab7UBpOlBo@{`5j4d za$C)~=-CL`Kk5 zuLxao)VH=5>j992Lc~40J{8T-dJ`MyvF48;khOkJaVt3;Tn@QVyz9AO=6^;#Z%!`! zY?3lgiGC7b(`A{6^xt`Gv+r;v7Y?h9Gn4Ou{Uu#9FE74w${ODBPJh`81H^sXNRDWN zBs-O6dfVP>p1UdyTDxaR^)aq-g(UCrSwNcZY%!si8$pAR4P(D{9e4+@9GV$c9w=6 zNk;>JGQ)(p1Hz-AUPw>5Y~U}NV6sTLrwE*24Vz$b6{2ZaJDUmXLl$5_TJ+( z2XFft3$qHbTzAV2Vagg^3Zo04T8t%(g)|8&xXraY&*u)zS#k#Gt^kt?bH}R5iFh+k zD8m|R`b1Fq>=S!Bz67k=3k?N`_6H*H6E3H>gj(><6)))CCEQLZZFC=%f%y|Ox}rE_ zw`k-A%w0sk$!?9Z?c%~XH4x0pQn2S7qKJ5kp1g5;lE+_t@8zI7vwW*ppj8I4aDA(O zUvLj`=J4d$uCeWoj}nY&f|GLN4hLe~cy>yx2%AgXB}i(s)9XL_0@1wwLQfhRy>I)c#6B0# zED;YI&H)7K!yp_-3jKRJNI_%ds+n>{r?xxttf8;S>sw`4joP%>PY;{Jk!GvlXN-?~ zd6*9ojRjko&xQ#H{pWk5?vlSg24bWmjramlGmxG1HM+v5`g><*5<@Z-ekyE5?0!RK zxzpkee29ffr@jvAyqxp>CufLzNw?dI zWrJ3<9|KnYbd0Sa>x?06|7w;FA7{oWY zSRl{TEqea!%G!6NV`ATWqjV*7ag2?ryf!q-7O#Wv?@&-VkOEF6k*4U;O@Vpw%Mai+gci}l9i+!drp5)TZH>VfLp1NnS+wP7bA zB7^?A_c&ljs_U&nR?iUqBtK<>#}3d@yKg7a#kT$}j&CqeZLo>^Mrlz^Cs8KANm}Ms zVf|6}oOgfKi7!NzB>;i^r&M>~f`mqqBM3IRE9Z-_cp*83)T`nbPL z<;eP&9=Xa;$08Hz`&fHNt6*YVHzoEfl4-3usXpDHj2uMNa3 zO{#QGBPA6qACF*oaYM6UDf?X>N9Ck~`svSPBN&O>ehNoRkI=v|g^lgCW_#NaP~9oL zs&?|aGWFv;4hSxIN)e{@k19~A2U&#ng2pR! zZIp1V+%+0kD%LWi8oBfOv%vGUSE|%;DeGMSM4uuRC^OPA^pLb#Sgi!##%B)2q^Q*W zkV1odHavk*b$hA!=Fve^MhPMf0?Hoqav_ae1Edwn1pE?RGjkuXeC|22bTAUVRx#>+ zVp`QL`FO|AOGdwB@DHmj3Z^O2f!NQKR`yS+u2TMv2O*>~%54hHn-NneW47 zYgnsA#C+Vz7RS_Dx_4^R#~R%^k#*(CgS-i%SnV}dwnL&ED_AzljMy9aH$1Rqdkvet zdxyJ787PxO2M&4s?st(V=84T6Sr&vleSw}V`PD29a`Racde~%qWaCTLM4&SdEF^im zu;3Pu5Wn;wS#vBIT0W8LCQzjqaBpnsn!kcW9zaT31W$VVnDHczWLc^4!Q#?Ko=P6+T(mEDH%d zW}GE$DCIOkD0p;3yIW|@?WSCsa%akO;rkcDa0tcPulyf~%slnXsaT0&V_Ac&i&)uX zKV|z5+oq;d@}$0K*%_)&T|_9yJk>pL%0KNQzYR$DztLYkm*76?J{Ii1b3NSA z;#mwLk)mh%)tRh4XS0(p#U*2hJ+a3TI-`t36O8??3ayfK5QQa_cOQMHnuF`?@?Iu8yTjvty6f>%U=Hn^;qpjVs7&e~I@4O+ z&Q6`6j{$u^zPGst6037ZimpJ4Z8(LTFq^!2ue=e0hvIVZ*2>6@OxFPgqDADsF}oo; za!#TVf8oo^epFT)Iy>p`+nRJuGGRwH_`c&&^)vjE-O6f%(8dlr1UiIJ0LwL>TN|~f z6x1#ce~AlTEVm;7EtGZ%JEZLqEVl7dWyM&;^p=pT>Yd3nN9WKfG^m&uo86)LPpGT_ z!$g%wi?n}nW_#ysd`-D-@3@p3004mVV#@yR<%k)>l`8b~~)qcD5+mbi8P6dm#=ndFtgV7>}JuhY^o)1v4V* z=SAMo5NIMT!+ocq9$H#xwjB=8NC~3XQY+!}&tR|U?D_5Lo(xx({!$4w<0)uMS;K50 z9zy#d}Poo=tNWqc3O>xv|fXvr+us?4?V zLOO_LM(-fL>|#eBi1HcC?+JRa3nN8S&lUba03~t7+>z%NrB*IyO-WkhnWnA?PysNa z@uln%`~43CqP?fvrFujbvNqFIsdtxhiBZSBzYWqDO9 zZQZAq)6`8W8MWqQ(_Hm-TUXST>dQ8in}R=Cz06}++fx};B(LLHafn`CVKL=tqd*tK z5HIoVq14@)E_lc;)8}w%(ge$dyN-GIbGITiv#vP7X|ne$iqH4P$3Mqa*QWJ?<2TVeO{ z(N$X60>zHgQk%#~db^)MCJE{2I<0q(efdXmoa4LRqyqXvYFBHcfG&%fYahlxW*&cP zO7>=pKwDRz8#vvrGetZnH?Cw6mKvc0?Ql%o^pHSQbZ8-#oiU`WFxLU(v?FsB>H1P^B`oLZ{IRy^hGI8!XW)$Vx@Z&q1{M_ zFiG&0nKTKt_>s6z-h$^&deaFL6L}`KDfp5x<%BVfHbOi^_%3q?fB8}4|1%MK4rY%+ a0is7r_-~BSTc3tm098c|g*rKC#Qy*>u||FX literal 0 HcmV?d00001 diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 6ea60737..cfa9e1d4 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -398,11 +398,15 @@ "type": "text", "placeholders": {} }, + "setupChatBackup": "Set up chat backup", + "iWroteDownTheKey": "I wrote down the key", + "yourChatBackupHasBeenSetUp": "Your chat backup has been set up.", "chatBackup": "Chat backup", "@chatBackup": { "type": "text", "placeholders": {} }, + "setupChatBackupDescription": "To protect your messages, we have generated a security key for you. Please keep this in a safe place, such as a password manager.", "chatBackupDescription": "Your chat backup is secured with a security key. Please make sure you don't lose it.", "@chatBackupDescription": { "type": "text", diff --git a/lib/pages/bootstrap_dialog.dart b/lib/pages/bootstrap_dialog.dart index f63abdd0..fcbc4793 100644 --- a/lib/pages/bootstrap_dialog.dart +++ b/lib/pages/bootstrap_dialog.dart @@ -1,6 +1,4 @@ import 'package:adaptive_dialog/adaptive_dialog.dart'; -import 'package:fluffychat/config/setting_keys.dart'; -import 'package:fluffychat/utils/matrix_sdk_extensions.dart/flutter_matrix_hive_database.dart'; import 'package:matrix/encryption.dart'; import 'package:matrix/encryption/utils/bootstrap.dart'; import 'package:matrix/matrix.dart'; @@ -67,12 +65,6 @@ class _BootstrapDialogState extends State { }); } - void cancelAndDontAskAgain() async { - await (widget.client.database as FlutterMatrixHiveStore) - .put(SettingKeys.dontAskForBootstrapKey, true); - Navigator.of(context, rootNavigator: false).pop(false); - } - @override Widget build(BuildContext context) { _wipe ??= widget.wipe; @@ -83,43 +75,58 @@ class _BootstrapDialogState extends State { titleText = L10n.of(context).loadingPleaseWait; if (bootstrap == null) { - titleText = L10n.of(context).chatBackup; - body = Text(L10n.of(context).chatBackupDescription); + titleText = L10n.of(context).setupChatBackup; + body = Column( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset('assets/backup.png', fit: BoxFit.contain), + Text(L10n.of(context).setupChatBackupDescription), + ], + ); buttons.add(AdaptiveFlatButton( label: L10n.of(context).next, onPressed: () => _createBootstrap(false), )); - buttons.add(AdaptiveFlatButton( - label: L10n.of(context).dontAskAgain, - onPressed: cancelAndDontAskAgain, - textColor: Colors.red, - )); } else if (bootstrap.newSsssKey?.recoveryKey != null && _recoveryKeyStored == false) { final key = bootstrap.newSsssKey.recoveryKey; titleText = L10n.of(context).securityKey; - body = Container( - alignment: Alignment.center, - width: 200, - height: 128, - child: Text( - key, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 18, - wordSpacing: 38, - fontFamily: 'monospace', + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: Icon(Icons.close), + onPressed: Navigator.of(context).pop, ), + title: Text(L10n.of(context).securityKey), + ), + body: ListView( + padding: const EdgeInsets.all(16.0), + children: [ + TextField( + minLines: 4, + maxLines: 4, + readOnly: true, + controller: TextEditingController(text: key), + ), + const SizedBox(height: 16), + ElevatedButton.icon( + icon: Icon(Icons.copy_outlined), + label: Text(L10n.of(context).copyToClipboard), + onPressed: () => Clipboard.setData(ClipboardData(text: key)), + ), + const SizedBox(height: 16), + ElevatedButton.icon( + style: ElevatedButton.styleFrom( + primary: Theme.of(context).secondaryHeaderColor, + onPrimary: Theme.of(context).primaryColor, + ), + icon: Icon(Icons.check_outlined), + label: Text(L10n.of(context).iWroteDownTheKey), + onPressed: () => setState(() => _recoveryKeyStored = true), + ), + ], ), ); - buttons.add(AdaptiveFlatButton( - label: L10n.of(context).copyToClipboard, - onPressed: () => Clipboard.setData(ClipboardData(text: key)), - )); - buttons.add(AdaptiveFlatButton( - label: L10n.of(context).next, - onPressed: () => setState(() => _recoveryKeyStored = true), - )); } else { switch (bootstrap.state) { case BootstrapState.loading: @@ -151,22 +158,20 @@ class _BootstrapDialogState extends State { break; case BootstrapState.openExistingSsss: _recoveryKeyStored = true; - titleText = - _recoveryKeyInputError ?? L10n.of(context).pleaseEnterSecurityKey; - body = PlatformInfos.isCupertinoStyle - ? CupertinoTextField( - minLines: 1, - maxLines: 1, - autofocus: true, - autocorrect: false, - autofillHints: _recoveryKeyInputLoading - ? null - : [AutofillHints.password], - controller: _recoveryKeyTextEditingController, - ) - : TextField( - minLines: 1, - maxLines: 1, + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: Icon(Icons.close), + onPressed: Navigator.of(context).pop, + ), + title: Text(L10n.of(context).pleaseEnterSecurityKey), + ), + body: ListView( + padding: const EdgeInsets.all(16.0), + children: [ + TextField( + minLines: 4, + maxLines: 4, autofocus: true, autocorrect: false, autofillHints: _recoveryKeyInputLoading @@ -174,63 +179,90 @@ class _BootstrapDialogState extends State { : [AutofillHints.password], controller: _recoveryKeyTextEditingController, decoration: InputDecoration( - border: UnderlineInputBorder(), - filled: false, - hintText: L10n.of(context).securityKey, + hintText: 'Abc123 Def456', + labelText: L10n.of(context).securityKey, + errorText: _recoveryKeyInputError, ), - ); - buttons.add(AdaptiveFlatButton( - label: L10n.of(context).unlockChatBackup, - onPressed: () async { - setState(() { - _recoveryKeyInputError = null; - _recoveryKeyInputLoading = true; - }); - try { - await bootstrap.newSsssKey.unlock( - keyOrPassphrase: _recoveryKeyTextEditingController.text, - ); - await bootstrap.openExistingSsss(); - } catch (e, s) { - Logs().w('Unable to unlock SSSS', e, s); - setState(() => _recoveryKeyInputError = - L10n.of(context).oopsSomethingWentWrong); - } finally { - setState(() => _recoveryKeyInputLoading = false); - } - })); - buttons.add(AdaptiveFlatButton( - label: L10n.of(context).transferFromAnotherDevice, - onPressed: () async { - final req = await showFutureLoadingDialog( - context: context, - future: () => widget.client.userDeviceKeys[widget.client.userID] - .startVerification(), - ); - if (req.error != null) return; - await KeyVerificationDialog(request: req.result).show(context); - Navigator.of(context, rootNavigator: false).pop(); - }, - )); - buttons.add(AdaptiveFlatButton( - textColor: Colors.red, - label: L10n.of(context).securityKeyLost, - onPressed: () async { - if (OkCancelResult.ok == - await showOkCancelAlertDialog( - useRootNavigator: false, - context: context, - title: L10n.of(context).securityKeyLost, - message: L10n.of(context).wipeChatBackup, - okLabel: L10n.of(context).ok, - cancelLabel: L10n.of(context).cancel, - isDestructiveAction: true, - )) { - _createBootstrap(true); - } - }, - )); - break; + ), + const SizedBox(height: 16), + ElevatedButton.icon( + icon: Icon(Icons.lock_open_outlined), + label: Text(L10n.of(context).unlockChatBackup), + onPressed: () async { + setState(() { + _recoveryKeyInputError = null; + _recoveryKeyInputLoading = true; + }); + try { + await bootstrap.newSsssKey.unlock( + keyOrPassphrase: + _recoveryKeyTextEditingController.text, + ); + await bootstrap.openExistingSsss(); + } catch (e, s) { + Logs().w('Unable to unlock SSSS', e, s); + setState(() => _recoveryKeyInputError = + L10n.of(context).oopsSomethingWentWrong); + } finally { + setState(() => _recoveryKeyInputLoading = false); + } + }), + const SizedBox(height: 16), + Row(children: [ + Expanded(child: Divider()), + Padding( + padding: const EdgeInsets.all(12.0), + child: Text(L10n.of(context).or), + ), + Expanded(child: Divider()), + ]), + const SizedBox(height: 16), + ElevatedButton.icon( + style: ElevatedButton.styleFrom( + primary: Theme.of(context).secondaryHeaderColor, + onPrimary: Theme.of(context).primaryColor, + ), + icon: Icon(Icons.transfer_within_a_station_outlined), + label: Text(L10n.of(context).transferFromAnotherDevice), + onPressed: () async { + final req = await showFutureLoadingDialog( + context: context, + future: () => widget + .client.userDeviceKeys[widget.client.userID] + .startVerification(), + ); + if (req.error != null) return; + await KeyVerificationDialog(request: req.result) + .show(context); + Navigator.of(context, rootNavigator: false).pop(); + }, + ), + const SizedBox(height: 16), + ElevatedButton.icon( + style: ElevatedButton.styleFrom( + primary: Theme.of(context).secondaryHeaderColor, + onPrimary: Colors.red, + ), + icon: Icon(Icons.delete_outlined), + label: Text(L10n.of(context).securityKeyLost), + onPressed: () async { + if (OkCancelResult.ok == + await showOkCancelAlertDialog( + useRootNavigator: false, + context: context, + title: L10n.of(context).securityKeyLost, + message: L10n.of(context).wipeChatBackup, + okLabel: L10n.of(context).ok, + cancelLabel: L10n.of(context).cancel, + isDestructiveAction: true, + )) { + _createBootstrap(true); + } + }, + ) + ], + ), + ); case BootstrapState.askWipeCrossSigning: WidgetsBinding.instance.addPostFrameCallback( (_) => bootstrap.wipeCrossSigning(_wipe), @@ -270,8 +302,8 @@ class _BootstrapDialogState extends State { body = Column( mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.check_circle, color: Colors.green, size: 40), - Text(L10n.of(context).keysCached), + Image.asset('assets/backup.png', fit: BoxFit.contain), + Text(L10n.of(context).yourChatBackupHasBeenSetUp), ], ); buttons.add(AdaptiveFlatButton( diff --git a/lib/widgets/encryption_button.dart b/lib/widgets/encryption_button.dart index 3d044ced..36cb8f45 100644 --- a/lib/widgets/encryption_button.dart +++ b/lib/widgets/encryption_button.dart @@ -95,7 +95,7 @@ class _EncryptionButtonState extends State { : (allUsersValid ? Colors.green : Colors.orange); } else if (!widget.room.encrypted && widget.room.joinRules != JoinRules.public) { - color = null; + color = Colors.red; } return IconButton( tooltip: widget.room.encrypted