recovery_key_dialog.dart 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import 'dart:io' as io;
  2. import 'dart:ui';
  3. import 'package:bip39/bip39.dart' as bip39;
  4. import 'package:flutter/material.dart';
  5. import 'package:flutter/services.dart';
  6. import 'package:flutter/widgets.dart';
  7. import 'package:photos/core/configuration.dart';
  8. import 'package:photos/core/constants.dart';
  9. import 'package:photos/utils/toast_util.dart';
  10. import 'package:share_plus/share_plus.dart';
  11. class RecoveryKeyDialog extends StatefulWidget {
  12. final String recoveryKey;
  13. final String doneText;
  14. final Function() onDone;
  15. final bool isDismissible;
  16. final String title;
  17. final String text;
  18. final String subText;
  19. RecoveryKeyDialog(
  20. this.recoveryKey,
  21. this.doneText,
  22. this.onDone, {
  23. this.title,
  24. this.text,
  25. this.subText,
  26. Key key,
  27. this.isDismissible = true,
  28. }) : super(key: key);
  29. @override
  30. _RecoveryKeyDialogState createState() => _RecoveryKeyDialogState();
  31. }
  32. class _RecoveryKeyDialogState extends State<RecoveryKeyDialog> {
  33. bool _hasTriedToSave = false;
  34. final _recoveryKeyFile = io.File(
  35. Configuration.instance.getTempDirectory() + "ente-recovery-key.txt");
  36. @override
  37. Widget build(BuildContext context) {
  38. final String recoveryKey = bip39.entropyToMnemonic(widget.recoveryKey);
  39. if (recoveryKey.split(' ').length != kMnemonicKeyWordCount) {
  40. throw AssertionError(
  41. 'recovery code should have $kMnemonicKeyWordCount words');
  42. }
  43. List<Widget> actions = [];
  44. if (!_hasTriedToSave) {
  45. actions.add(TextButton(
  46. child: Text(
  47. "save later",
  48. style: TextStyle(
  49. color: Colors.red,
  50. ),
  51. ),
  52. onPressed: () async {
  53. await _saveKeys();
  54. },
  55. ));
  56. }
  57. actions.add(
  58. TextButton(
  59. child: Text(
  60. "save",
  61. style: TextStyle(
  62. color: Theme.of(context).buttonColor,
  63. ),
  64. ),
  65. onPressed: () {
  66. _shareRecoveryKey(recoveryKey);
  67. },
  68. ),
  69. );
  70. if (_hasTriedToSave) {
  71. actions.add(
  72. TextButton(
  73. child: Text(
  74. widget.doneText,
  75. style: TextStyle(
  76. color: Colors.white,
  77. ),
  78. ),
  79. onPressed: () async {
  80. await _saveKeys();
  81. },
  82. ),
  83. );
  84. }
  85. return WillPopScope(
  86. onWillPop: () async => widget.isDismissible,
  87. child: AlertDialog(
  88. title: Text(widget.title ?? "recovery key"),
  89. content: SingleChildScrollView(
  90. child: Column(
  91. mainAxisAlignment: MainAxisAlignment.start,
  92. crossAxisAlignment: CrossAxisAlignment.start,
  93. children: [
  94. Text(
  95. widget.text ??
  96. "if you forget your password, the only way you can recover your data is with this key",
  97. style: TextStyle(height: 1.2),
  98. ),
  99. Padding(padding: EdgeInsets.all(8)),
  100. GestureDetector(
  101. onTap: () async {
  102. await Clipboard.setData(ClipboardData(text: recoveryKey));
  103. showToast("recovery key copied to clipboard");
  104. setState(() {
  105. _hasTriedToSave = true;
  106. });
  107. },
  108. child: Container(
  109. padding: EdgeInsets.all(16),
  110. child: Center(
  111. child: Text(
  112. recoveryKey,
  113. style: TextStyle(
  114. fontSize: 16,
  115. fontFeatures: const [FontFeature.tabularFigures()],
  116. color: Colors.white.withOpacity(0.7),
  117. ),
  118. ),
  119. ),
  120. color: Colors.white.withOpacity(0.1),
  121. ),
  122. ),
  123. Padding(padding: EdgeInsets.all(8)),
  124. Text(
  125. widget.subText ?? "we don't store this key",
  126. ),
  127. Padding(padding: EdgeInsets.all(8)),
  128. Text(
  129. "please save this in a safe place",
  130. ),
  131. ],
  132. ),
  133. ),
  134. actions: actions,
  135. ),
  136. );
  137. }
  138. Future _shareRecoveryKey(String recoveryKey) async {
  139. if (_recoveryKeyFile.existsSync()) {
  140. await _recoveryKeyFile.delete();
  141. }
  142. _recoveryKeyFile.writeAsStringSync(recoveryKey);
  143. await Share.shareFiles([_recoveryKeyFile.path]);
  144. Future.delayed(Duration(milliseconds: 500), () {
  145. if (mounted) {
  146. setState(() {
  147. _hasTriedToSave = true;
  148. });
  149. }
  150. });
  151. }
  152. Future<void> _saveKeys() async {
  153. Navigator.of(context, rootNavigator: true).pop();
  154. if (_recoveryKeyFile.existsSync()) {
  155. await _recoveryKeyFile.delete();
  156. }
  157. widget.onDone();
  158. }
  159. }