plain_text_import.dart 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import 'dart:async';
  2. import 'dart:io';
  3. import 'dart:ui';
  4. import 'package:ente_auth/ente_theme_data.dart';
  5. import 'package:ente_auth/l10n/l10n.dart';
  6. import 'package:ente_auth/models/code.dart';
  7. import 'package:ente_auth/services/authenticator_service.dart';
  8. import 'package:ente_auth/store/code_store.dart';
  9. import 'package:ente_auth/ui/settings/data/import/import_success.dart';
  10. import 'package:ente_auth/utils/dialog_util.dart';
  11. import 'package:file_picker/file_picker.dart';
  12. import 'package:flutter/material.dart';
  13. import 'package:logging/logging.dart';
  14. class PlainTextImport extends StatelessWidget {
  15. const PlainTextImport({super.key});
  16. @override
  17. Widget build(BuildContext context) {
  18. final l10n = context.l10n;
  19. return Column(
  20. children: [
  21. Text(
  22. l10n.importInstruction,
  23. ),
  24. const SizedBox(
  25. height: 20,
  26. ),
  27. Container(
  28. color: Theme.of(context).colorScheme.gNavBackgroundColor,
  29. child: Padding(
  30. padding: const EdgeInsets.all(8),
  31. child: Text(
  32. "otpauth://totp/provider.com:you@email.com?secret=YOUR_SECRET",
  33. style: TextStyle(
  34. fontFeatures: const [FontFeature.tabularFigures()],
  35. fontFamily: Platform.isIOS ? "Courier" : "monospace",
  36. fontSize: 13,
  37. ),
  38. ),
  39. ),
  40. ),
  41. const SizedBox(
  42. height: 20,
  43. ),
  44. Text(l10n.importCodeDelimiterInfo),
  45. ],
  46. );
  47. }
  48. }
  49. Future<void> showImportInstructionDialog(BuildContext context) async {
  50. final l10n = context.l10n;
  51. final AlertDialog alert = AlertDialog(
  52. shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
  53. title: Text(
  54. l10n.importCodes,
  55. style: Theme.of(context).textTheme.titleLarge,
  56. ),
  57. content: const SingleChildScrollView(
  58. child: PlainTextImport(),
  59. ),
  60. actions: [
  61. TextButton(
  62. child: Text(
  63. l10n.cancel,
  64. style: const TextStyle(
  65. color: Colors.red,
  66. ),
  67. ),
  68. onPressed: () {
  69. Navigator.of(context, rootNavigator: true).pop('dialog');
  70. },
  71. ),
  72. TextButton(
  73. child: Text(l10n.selectFile),
  74. onPressed: () {
  75. Navigator.of(context, rootNavigator: true).pop('dialog');
  76. _pickImportFile(context);
  77. },
  78. ),
  79. ],
  80. );
  81. return showDialog(
  82. context: context,
  83. builder: (BuildContext context) {
  84. return alert;
  85. },
  86. barrierColor: Colors.black12,
  87. );
  88. }
  89. Future<void> _pickImportFile(BuildContext context) async {
  90. final l10n = context.l10n;
  91. FilePickerResult? result = await FilePicker.platform.pickFiles();
  92. if (result == null) {
  93. return;
  94. }
  95. final progressDialog = createProgressDialog(context, l10n.pleaseWait);
  96. await progressDialog.show();
  97. try {
  98. File file = File(result.files.single.path!);
  99. final codes = await file.readAsString();
  100. List<String> splitCodes = codes.split(",");
  101. if (splitCodes.length == 1) {
  102. splitCodes = codes.split("\n");
  103. }
  104. final parsedCodes = [];
  105. for (final code in splitCodes) {
  106. try {
  107. parsedCodes.add(Code.fromRawData(code));
  108. } catch (e) {
  109. Logger('PlainText').severe("Could not parse code", e);
  110. }
  111. }
  112. for (final code in parsedCodes) {
  113. await CodeStore.instance.addCode(code, shouldSync: false);
  114. }
  115. unawaited(AuthenticatorService.instance.onlineSync());
  116. await progressDialog.hide();
  117. await importSuccessDialog(context, parsedCodes.length);
  118. } catch (e) {
  119. await progressDialog.hide();
  120. await showErrorDialog(
  121. context,
  122. context.l10n.sorry,
  123. context.l10n.importFailureDesc,
  124. );
  125. }
  126. }