From 562f5667eb8098c9119a7d34ff934dad0f8d0920 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 31 Jul 2023 13:00:38 +0530 Subject: [PATCH] Show multiple import options --- lib/ui/settings/data/data_section_widget.dart | 145 +---------------- .../settings/data/import/import_service.dart | 20 +++ .../data/import/plain_text_import.dart | 152 ++++++++++++++++++ lib/ui/settings/data/import_page.dart | 106 ++++++++++++ 4 files changed, 282 insertions(+), 141 deletions(-) create mode 100644 lib/ui/settings/data/import/import_service.dart create mode 100644 lib/ui/settings/data/import/plain_text_import.dart create mode 100644 lib/ui/settings/data/import_page.dart diff --git a/lib/ui/settings/data/data_section_widget.dart b/lib/ui/settings/data/data_section_widget.dart index 021e870c6b..1d3872f035 100644 --- a/lib/ui/settings/data/data_section_widget.dart +++ b/lib/ui/settings/data/data_section_widget.dart @@ -1,33 +1,19 @@ -import 'dart:async'; -import 'dart:io'; -import 'dart:ui'; -import 'package:ente_auth/core/configuration.dart'; -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/theme/ente_theme.dart'; import 'package:ente_auth/ui/components/captioned_text_widget.dart'; -import 'package:ente_auth/ui/components/dialog_widget.dart'; import 'package:ente_auth/ui/components/expandable_menu_item_widget.dart'; import 'package:ente_auth/ui/components/menu_item_widget.dart'; -import 'package:ente_auth/ui/components/models/button_type.dart'; import 'package:ente_auth/ui/settings/common_settings.dart'; import 'package:ente_auth/ui/settings/data/export_widget.dart'; -import 'package:ente_auth/utils/dialog_util.dart'; -import 'package:file_picker/file_picker.dart'; +import 'package:ente_auth/ui/settings/data/import_page.dart'; +import 'package:ente_auth/utils/navigation_util.dart'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; class DataSectionWidget extends StatelessWidget { final _logger = Logger("AccountSectionWidget"); - final _codeFile = File( - Configuration.instance.getTempDirectory() + "ente-authenticator-codes.txt", - ); - DataSectionWidget({Key? key}) : super(key: key); @override @@ -53,7 +39,8 @@ class DataSectionWidget extends StatelessWidget { trailingIcon: Icons.chevron_right_outlined, trailingIconIsMuted: true, onTap: () async { - _showImportInstructionDialog(context); + routeToPage(context, ImportCodePage()); + // _showImportInstructionDialog(context); }, ), sectionOptionSpacing, @@ -74,128 +61,4 @@ class DataSectionWidget extends StatelessWidget { children: children, ); } - - 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: SingleChildScrollView( - child: 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), - ], - ), - ), - 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 dialog = createProgressDialog(context, l10n.pleaseWait); - await dialog.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.severe("Could not parse code", e); - } - } - for (final code in parsedCodes) { - await CodeStore.instance.addCode(code, shouldSync: false); - } - unawaited(AuthenticatorService.instance.sync()); - - 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('dialog'); - }, - firstButtonType: ButtonType.primary, - ); - await showConfettiDialog( - context: context, - dialogBuilder: (BuildContext context) { - return dialog; - }, - ); - } catch (e) { - await dialog.hide(); - await showErrorDialog( - context, - context.l10n.sorry, - context.l10n.importFailureDesc, - ); - } - } } diff --git a/lib/ui/settings/data/import/import_service.dart b/lib/ui/settings/data/import/import_service.dart new file mode 100644 index 0000000000..d913425f97 --- /dev/null +++ b/lib/ui/settings/data/import/import_service.dart @@ -0,0 +1,20 @@ + +import 'package:ente_auth/ui/settings/data/import/plain_text_import.dart'; +import 'package:ente_auth/ui/settings/data/import_page.dart'; +import 'package:ente_auth/utils/toast_util.dart'; +import 'package:flutter/cupertino.dart'; + +class ImportService { + + static final ImportService _instance = ImportService._internal(); + factory ImportService() => _instance; + ImportService._internal(); + + Future initiateImport(BuildContext context,ImportType type) async { + if(type == ImportType.plainText) { + showImportInstructionDialog(context); + } else { + showToast(context, 'Coming soon!'); + } + } +} \ No newline at end of file diff --git a/lib/ui/settings/data/import/plain_text_import.dart b/lib/ui/settings/data/import/plain_text_import.dart new file mode 100644 index 0000000000..493248dc3b --- /dev/null +++ b/lib/ui/settings/data/import/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 dialog = createProgressDialog(context, l10n.pleaseWait); + await dialog.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()); + + 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('dialog'); + }, + firstButtonType: ButtonType.primary, + ); + await showConfettiDialog( + context: context, + dialogBuilder: (BuildContext context) { + return dialog; + }, + ); + } catch (e) { + await dialog.hide(); + await showErrorDialog( + context, + context.l10n.sorry, + context.l10n.importFailureDesc, + ); + } +} diff --git a/lib/ui/settings/data/import_page.dart b/lib/ui/settings/data/import_page.dart new file mode 100644 index 0000000000..7be1fcf00b --- /dev/null +++ b/lib/ui/settings/data/import_page.dart @@ -0,0 +1,106 @@ +import 'package:ente_auth/l10n/l10n.dart'; +import 'package:ente_auth/theme/ente_theme.dart'; +import 'package:ente_auth/ui/components/buttons/icon_button_widget.dart'; +import 'package:ente_auth/ui/components/captioned_text_widget.dart'; +import 'package:ente_auth/ui/components/divider_widget.dart'; +import 'package:ente_auth/ui/components/menu_item_widget.dart'; +import 'package:ente_auth/ui/components/title_bar_title_widget.dart'; +import 'package:ente_auth/ui/components/title_bar_widget.dart'; +import 'package:ente_auth/ui/settings/data/import/import_service.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +enum ImportType { + plainText, + encrypted, + ravio, +} + +class ImportCodePage extends StatelessWidget { + late List importOptions = [ + ImportType.plainText, + ImportType.encrypted, + ImportType.ravio, + ]; + + ImportCodePage({super.key}); + + String getTitle(BuildContext context, ImportType type) { + switch (type) { + case ImportType.plainText: + return 'Plain text'; + case ImportType.encrypted: + return 'ente Encrypted export'; + case ImportType.ravio: + return 'Ravio'; + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: CustomScrollView( + primary: false, + slivers: [ + TitleBarWidget( + flexibleSpaceTitle: TitleBarTitleWidget( + title: context.l10n.importCodes, + ), + flexibleSpaceCaption: "Import source", + actionIcons: [ + IconButtonWidget( + icon: Icons.close_outlined, + iconButtonType: IconButtonType.secondary, + onTap: () { + Navigator.pop(context); + Navigator.pop(context); + }, + ), + ], + ), + SliverList( + delegate: SliverChildBuilderDelegate( + (delegateBuildContext, index) { + final type = importOptions[index]; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + children: [ + if (index == 0) + const SizedBox( + height: 24, + ), + MenuItemWidget( + captionedTextWidget: CaptionedTextWidget( + title: getTitle(context, type), + ), + alignCaptionedTextToLeft: true, + menuItemColor: getEnteColorScheme(context).fillFaint, + pressedColor: getEnteColorScheme(context).fillFaint, + trailingIcon: Icons.chevron_right_outlined, + isBottomBorderRadiusRemoved: + index != importOptions.length - 1, + isTopBorderRadiusRemoved: index != 0, + onTap: () async { + ImportService().initiateImport(context, type); + // routeToPage(context, ImportCodePage()); + // _showImportInstructionDialog(context); + }, + ), + if (index != importOptions.length - 1) + DividerWidget( + dividerType: DividerType.menu, + bgColor: getEnteColorScheme(context).fillFaint, + ), + ], + ), + ); + }, + childCount: importOptions.length, + ), + ), + ], + ), + ); + } +}