From 94eb5d8c97b43b083d8f3f3cd09191fa3f0460a2 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 31 Jul 2023 15:26:08 +0530 Subject: [PATCH] Add support for importing raivo codes --- .../data/import/ravio_plain_text_import.dart | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 lib/ui/settings/data/import/ravio_plain_text_import.dart diff --git a/lib/ui/settings/data/import/ravio_plain_text_import.dart b/lib/ui/settings/data/import/ravio_plain_text_import.dart new file mode 100644 index 0000000000..ac408fc43d --- /dev/null +++ b/lib/ui/settings/data/import/ravio_plain_text_import.dart @@ -0,0 +1,152 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:ui'; + +import 'package:ente_auth/ente_theme_data.dart'; +import 'package:ente_auth/l10n/l10n.dart'; +import 'package:ente_auth/models/code.dart'; +import 'package:ente_auth/services/authenticator_service.dart'; +import 'package:ente_auth/store/code_store.dart'; +import 'package:ente_auth/ui/components/dialog_widget.dart'; +import 'package:ente_auth/ui/components/models/button_type.dart'; +import 'package:ente_auth/utils/dialog_util.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:logging/logging.dart'; + +class PlainTextImport extends StatelessWidget { + const PlainTextImport({super.key}); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + return Column( + children: [ + Text( + l10n.importInstruction, + ), + const SizedBox( + height: 20, + ), + Container( + color: Theme.of(context).colorScheme.gNavBackgroundColor, + child: Padding( + padding: const EdgeInsets.all(8), + child: Text( + "otpauth://totp/provider.com:you@email.com?secret=YOUR_SECRET", + style: TextStyle( + fontFeatures: const [FontFeature.tabularFigures()], + fontFamily: Platform.isIOS ? "Courier" : "monospace", + fontSize: 13, + ), + ), + ), + ), + const SizedBox( + height: 20, + ), + Text(l10n.importCodeDelimiterInfo), + ], + ); + } + +} + + +Future showImportInstructionDialog(BuildContext context) async { + final l10n = context.l10n; + final AlertDialog alert = AlertDialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + title: Text( + l10n.importCodes, + style: Theme.of(context).textTheme.headline6, + ), + content: const SingleChildScrollView( + child: PlainTextImport(), + ), + actions: [ + TextButton( + child: Text( + l10n.cancel, + style: const TextStyle( + color: Colors.red, + ), + ), + onPressed: () { + Navigator.of(context, rootNavigator: true).pop('dialog'); + }, + ), + TextButton( + child: Text(l10n.selectFile), + onPressed: () { + Navigator.of(context, rootNavigator: true).pop('dialog'); + _pickImportFile(context); + }, + ), + ], + ); + + return showDialog( + context: context, + builder: (BuildContext context) { + return alert; + }, + barrierColor: Colors.black12, + ); +} + + +Future _pickImportFile(BuildContext context) async { + final l10n = context.l10n; + FilePickerResult? result = await FilePicker.platform.pickFiles(); + if (result == null) { + return; + } + final progressDialog = createProgressDialog(context, l10n.pleaseWait); + await progressDialog.show(); + try { + File file = File(result.files.single.path!); + final codes = await file.readAsString(); + List splitCodes = codes.split(","); + if (splitCodes.length == 1) { + splitCodes = codes.split("\n"); + } + final parsedCodes = []; + for (final code in splitCodes) { + try { + parsedCodes.add(Code.fromRawData(code)); + } catch (e) { + Logger('PlainText').severe("Could not parse code", e); + } + } + for (final code in parsedCodes) { + await CodeStore.instance.addCode(code, shouldSync: false); + } + unawaited(AuthenticatorService.instance.sync()); + await progressDialog.hide(); + final DialogWidget dialog = choiceDialog( + title: context.l10n.importSuccessTitle, + body: context.l10n.importSuccessDesc(parsedCodes.length), + // body: "You have imported " + parsedCodes.length.toString() + " codes!", + firstButtonLabel: l10n.ok, + firstButtonOnTap: () async { + Navigator.of(context, rootNavigator: true).pop('progressDialog'); + }, + firstButtonType: ButtonType.primary, + ); + await showConfettiDialog( + context: context, + dialogBuilder: (BuildContext context) { + return dialog; + }, + ); + } catch (e) { + await progressDialog.hide(); + await showErrorDialog( + context, + context.l10n.sorry, + context.l10n.importFailureDesc, + ); + } +}