two_factor_setup_page.dart 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. import 'dart:ui';
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter/services.dart';
  4. import 'package:flutter_sodium/flutter_sodium.dart';
  5. import 'package:photos/core/configuration.dart';
  6. import 'package:photos/ente_theme_data.dart';
  7. import 'package:photos/services/user_service.dart';
  8. import 'package:photos/ui/account/recovery_key_page.dart';
  9. import 'package:photos/ui/lifecycle_event_handler.dart';
  10. import 'package:photos/utils/navigation_util.dart';
  11. import 'package:photos/utils/toast_util.dart';
  12. import 'package:pinput/pin_put/pin_put.dart';
  13. class TwoFactorSetupPage extends StatefulWidget {
  14. final String secretCode;
  15. final String qrCode;
  16. const TwoFactorSetupPage(this.secretCode, this.qrCode, {Key key})
  17. : super(key: key);
  18. @override
  19. State<TwoFactorSetupPage> createState() => _TwoFactorSetupPageState();
  20. }
  21. class _TwoFactorSetupPageState extends State<TwoFactorSetupPage>
  22. with SingleTickerProviderStateMixin {
  23. TabController _tabController;
  24. final _pinController = TextEditingController();
  25. final _pinPutDecoration = BoxDecoration(
  26. border: Border.all(color: const Color.fromRGBO(45, 194, 98, 1.0)),
  27. borderRadius: BorderRadius.circular(15.0),
  28. );
  29. String _code = "";
  30. ImageProvider _imageProvider;
  31. LifecycleEventHandler _lifecycleEventHandler;
  32. @override
  33. void initState() {
  34. _tabController = TabController(length: 2, vsync: this);
  35. _imageProvider = Image.memory(
  36. Sodium.base642bin(widget.qrCode),
  37. height: 180,
  38. width: 180,
  39. ).image;
  40. _lifecycleEventHandler = LifecycleEventHandler(
  41. resumeCallBack: () async {
  42. if (mounted) {
  43. final data = await Clipboard.getData(Clipboard.kTextPlain);
  44. if (data != null && data.text != null && data.text.length == 6) {
  45. _pinController.text = data.text;
  46. }
  47. }
  48. },
  49. );
  50. WidgetsBinding.instance.addObserver(_lifecycleEventHandler);
  51. super.initState();
  52. }
  53. @override
  54. void dispose() {
  55. WidgetsBinding.instance.removeObserver(_lifecycleEventHandler);
  56. super.dispose();
  57. }
  58. @override
  59. Widget build(BuildContext context) {
  60. return Scaffold(
  61. appBar: AppBar(
  62. elevation: 0,
  63. title: const Text(
  64. "Two-factor setup",
  65. ),
  66. ),
  67. body: _getBody(),
  68. );
  69. }
  70. Widget _getBody() {
  71. return SingleChildScrollView(
  72. reverse: true,
  73. child: Center(
  74. child: Column(
  75. crossAxisAlignment: CrossAxisAlignment.center,
  76. children: [
  77. SizedBox(
  78. height: 360,
  79. child: Column(
  80. children: [
  81. TabBar(
  82. labelColor: Theme.of(context).colorScheme.greenAlternative,
  83. unselectedLabelColor: Colors.grey,
  84. tabs: const [
  85. Tab(
  86. text: "Enter code",
  87. ),
  88. Tab(
  89. text: "Scan code",
  90. )
  91. ],
  92. controller: _tabController,
  93. indicatorSize: TabBarIndicatorSize.tab,
  94. ),
  95. Expanded(
  96. child: TabBarView(
  97. controller: _tabController,
  98. children: [
  99. _getSecretCode(),
  100. _getBarCode(),
  101. ],
  102. ),
  103. ),
  104. ],
  105. ),
  106. ),
  107. Divider(
  108. height: 1,
  109. thickness: 1,
  110. color: Theme.of(context).colorScheme.secondary,
  111. ),
  112. _getVerificationWidget(),
  113. ],
  114. ),
  115. ),
  116. );
  117. }
  118. Widget _getSecretCode() {
  119. final Color textColor = Theme.of(context).colorScheme.onSurface;
  120. return GestureDetector(
  121. onTap: () async {
  122. await Clipboard.setData(ClipboardData(text: widget.secretCode));
  123. showToast(context, "Code copied to clipboard");
  124. },
  125. child: Column(
  126. mainAxisAlignment: MainAxisAlignment.center,
  127. children: [
  128. const Padding(padding: EdgeInsets.all(12)),
  129. const Text(
  130. "Copy-paste this code\nto your authenticator app",
  131. style: TextStyle(
  132. height: 1.4,
  133. fontSize: 16,
  134. ),
  135. textAlign: TextAlign.center,
  136. ),
  137. const Padding(padding: EdgeInsets.all(16)),
  138. Padding(
  139. padding: const EdgeInsets.only(left: 10, right: 10),
  140. child: Container(
  141. padding: const EdgeInsets.all(16),
  142. color: textColor.withOpacity(0.1),
  143. child: Center(
  144. child: Text(
  145. widget.secretCode,
  146. style: TextStyle(
  147. fontSize: 15,
  148. fontFeatures: const [FontFeature.tabularFigures()],
  149. color: textColor.withOpacity(0.7),
  150. ),
  151. ),
  152. ),
  153. ),
  154. ),
  155. const Padding(padding: EdgeInsets.all(6)),
  156. Text(
  157. "tap to copy",
  158. style: TextStyle(color: textColor.withOpacity(0.5)),
  159. )
  160. ],
  161. ),
  162. );
  163. }
  164. Widget _getBarCode() {
  165. return Center(
  166. child: Column(
  167. children: [
  168. const Padding(padding: EdgeInsets.all(12)),
  169. const Text(
  170. "Scan this barcode with\nyour authenticator app",
  171. style: TextStyle(
  172. height: 1.4,
  173. fontSize: 16,
  174. ),
  175. textAlign: TextAlign.center,
  176. ),
  177. const Padding(padding: EdgeInsets.all(12)),
  178. Image(
  179. image: _imageProvider,
  180. height: 180,
  181. width: 180,
  182. ),
  183. ],
  184. ),
  185. );
  186. }
  187. Widget _getVerificationWidget() {
  188. return Column(
  189. children: [
  190. const Padding(padding: EdgeInsets.all(12)),
  191. const Text(
  192. "Enter the 6-digit code from\nyour authenticator app",
  193. style: TextStyle(
  194. height: 1.4,
  195. fontSize: 16,
  196. ),
  197. textAlign: TextAlign.center,
  198. ),
  199. const Padding(padding: EdgeInsets.all(16)),
  200. Padding(
  201. padding: const EdgeInsets.fromLTRB(40, 0, 40, 0),
  202. child: PinPut(
  203. fieldsCount: 6,
  204. onSubmit: (String code) {
  205. _enableTwoFactor(code);
  206. },
  207. onChanged: (String pin) {
  208. setState(() {
  209. _code = pin;
  210. });
  211. },
  212. controller: _pinController,
  213. submittedFieldDecoration: _pinPutDecoration.copyWith(
  214. borderRadius: BorderRadius.circular(20.0),
  215. ),
  216. selectedFieldDecoration: _pinPutDecoration,
  217. followingFieldDecoration: _pinPutDecoration.copyWith(
  218. borderRadius: BorderRadius.circular(5.0),
  219. border: Border.all(
  220. color: const Color.fromRGBO(45, 194, 98, 0.5),
  221. ),
  222. ),
  223. inputDecoration: const InputDecoration(
  224. focusedBorder: InputBorder.none,
  225. border: InputBorder.none,
  226. counterText: '',
  227. ),
  228. ),
  229. ),
  230. const Padding(padding: EdgeInsets.all(24)),
  231. OutlinedButton(
  232. onPressed: _code.length == 6
  233. ? () async {
  234. _enableTwoFactor(_code);
  235. }
  236. : null,
  237. child: const Text("Confirm"),
  238. ),
  239. const Padding(padding: EdgeInsets.only(bottom: 24)),
  240. ],
  241. );
  242. }
  243. Future<void> _enableTwoFactor(String code) async {
  244. final success = await UserService.instance
  245. .enableTwoFactor(context, widget.secretCode, code);
  246. if (success) {
  247. _showSuccessPage();
  248. }
  249. }
  250. void _showSuccessPage() {
  251. final recoveryKey = Sodium.bin2hex(Configuration.instance.getRecoveryKey());
  252. routeToPage(
  253. context,
  254. RecoveryKeyPage(
  255. recoveryKey,
  256. "OK",
  257. showAppBar: true,
  258. onDone: () {},
  259. title: "⚡ setup complete",
  260. text: "save your recovery key if you haven't already",
  261. subText:
  262. "this can be used to recover your account if you lose your second factor",
  263. ),
  264. );
  265. }
  266. }