two_factor_setup_page.dart 8.0 KB

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