Show multiple import options
This commit is contained in:
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
20
lib/ui/settings/data/import/import_service.dart
Normal file
20
lib/ui/settings/data/import/import_service.dart
Normal 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!');
|
||||
}
|
||||
}
|
||||
}
|
||||
152
lib/ui/settings/data/import/plain_text_import.dart
Normal file
152
lib/ui/settings/data/import/plain_text_import.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
106
lib/ui/settings/data/import_page.dart
Normal file
106
lib/ui/settings/data/import_page.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user