Show multiple import options

This commit is contained in:
Neeraj Gupta
2023-07-31 13:00:38 +05:30
parent ea770cfa18
commit 562f5667eb
4 changed files with 282 additions and 141 deletions

View File

@@ -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<void> _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<void> _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<String> 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,
);
}
}
}

View File

@@ -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<void> initiateImport(BuildContext context,ImportType type) async {
if(type == ImportType.plainText) {
showImportInstructionDialog(context);
} else {
showToast(context, 'Coming soon!');
}
}
}

View File

@@ -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<void> 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<void> _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<String> 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,
);
}
}

View File

@@ -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<ImportType> 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: <Widget>[
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,
),
),
],
),
);
}
}