Skip to content

Commit

Permalink
CSV import implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
GleammerRay committed Apr 30, 2023
1 parent f9b1cd6 commit efb59aa
Show file tree
Hide file tree
Showing 7 changed files with 494 additions and 9 deletions.
15 changes: 13 additions & 2 deletions lib/l10n/app_en.arb
Expand Up @@ -85,7 +85,7 @@
"lastName": "Last name",
"gender": "Gender",
"email": "Email",
"phoneNumber": "phoneNumber",
"phoneNumber": "Phone number",
"firstAddresssLine": "First address line",
"secondAddressLine": "Second address line",
"zipCode": "Zip code",
Expand Down Expand Up @@ -209,5 +209,16 @@
"notSpecified": "Not specified",
"male": "Male",
"female": "Female",
"other": "Other"
"other": "Other",
"csvImport": "CSV import",
"importPasswords": "Import passwords",
"importPaymentCards": "Import payment cards",
"importNotes": "Import notes",
"importIDCards": "Import ID cards",
"importIdentities": "Import identities",
"noCSVDataFound": "No CSV data found",
"csvImportMessage1":"Select CSV field values to match the entry variables",
"csvImportMessage2": "This template will be used for all CSV entries",
"@csvImportMessage1": { "description": "csvImportMessage1 and csvImportMessage2 make up a single message used in the CSV import entries screen" },
"@csvImportMessage2": { "description": "csvImportMessage1 and csvImportMessage2 make up a single message used in the CSV import entries screen" }
}
5 changes: 5 additions & 0 deletions lib/main.dart
Expand Up @@ -18,6 +18,8 @@ import 'screens/change_password_screen.dart';
import 'screens/change_username_screen.dart';
import 'screens/confirm_restore_screen.dart';
import 'screens/credentials_screen.dart';
import 'screens/csv_import_screen.dart';
import 'screens/csv_import_entries_screen.dart';
import 'screens/remove_account_screen.dart';
import 'screens/setup_screen.dart';
import 'screens/security_screen.dart';
Expand Down Expand Up @@ -120,6 +122,9 @@ class Passy extends StatelessWidget {
const ConfirmRestoreScreen(),
ConnectScreen.routeName: (context) => const ConnectScreen(),
CredentialsScreen.routeName: (context) => const CredentialsScreen(),
CSVImportScreen.routeName: (context) => const CSVImportScreen(),
CSVImportEntriesScreen.routeName: (context) =>
const CSVImportEntriesScreen(),
EditCustomFieldScreen.routeName: (context) =>
const EditCustomFieldScreen(),
EditIDCardScreen.routeName: (context) => const EditIDCardScreen(),
Expand Down
14 changes: 8 additions & 6 deletions lib/passy_flutter/common/common.dart
Expand Up @@ -58,12 +58,14 @@ String dateToString(DateTime date) {
DateTime stringToDate(String value) {
if (value == '') return DateTime.now();
List<String> _dateSplit = value.split('/');
if (_dateSplit.length == 3) return DateTime.now();
return DateTime(
int.parse(_dateSplit[2]),
int.parse(_dateSplit[1]),
int.parse(_dateSplit[0]),
);
if (_dateSplit.length < 3) return DateTime.now();
int? yy = int.tryParse(_dateSplit[2]);
if (yy == null) return DateTime.now();
int? mm = int.tryParse(_dateSplit[1]);
if (mm == null) return DateTime.now();
int? dd = int.tryParse(_dateSplit[0]);
if (dd == null) return DateTime.now();
return DateTime(yy, mm, dd);
}

Future<DateTime?> showPassyDatePicker(
Expand Down
246 changes: 246 additions & 0 deletions lib/screens/csv_import_entries_screen.dart
@@ -0,0 +1,246 @@
import 'package:flutter/material.dart';
import 'package:passy/common/common.dart';
import 'package:passy/passy_data/entry_type.dart';
import 'package:passy/passy_data/passy_entry.dart';
import 'package:passy/passy_flutter/passy_flutter.dart';
import 'common.dart';
import 'csv_import_screen.dart';
import 'log_screen.dart';

class CSVImportEntriesScreenArguments {
Widget title;
EntryType entryType;
Map<String, dynamic> entryJson;
List<List<String>> entries;

CSVImportEntriesScreenArguments({
required this.title,
required this.entryType,
required this.entryJson,
required this.entries,
});
}

class CSVImportEntriesScreen extends StatefulWidget {
const CSVImportEntriesScreen({Key? key}) : super(key: key);

static const routeName = '${CSVImportScreen.routeName}/entries';

@override
State<StatefulWidget> createState() => _CSVImportEntriesScreen();
}

class _CSVImportEntriesScreen extends State<CSVImportEntriesScreen> {
@override
Widget build(BuildContext context) {
CSVImportEntriesScreenArguments args = ModalRoute.of(context)!
.settings
.arguments as CSVImportEntriesScreenArguments;
Map<String, int> jsonToCSV = {};
for (MapEntry<String, dynamic> entryJsonEntry in args.entryJson.entries) {
dynamic entryJsonValue = entryJsonEntry.value;
if (entryJsonValue is! String) continue;
String entryJsonKey = entryJsonEntry.key;
if (entryJsonKey == 'iconName') continue;
if (entryJsonKey == 'key') continue;
jsonToCSV[entryJsonKey] = -1;
}
List<String> entryJsonKeys = jsonToCSV.keys.toList();
List<DropdownMenuItem<int>> items = [
const DropdownMenuItem(
child: Text('Empty'),
value: -1,
),
];
if (args.entries.isNotEmpty) {
List<String> entry = args.entries.first;
for (int i = 0; i != entry.length; i++) {
dynamic entryValue = entry[i];
if (entryValue is! String) continue;
items.add(DropdownMenuItem(
child: Text(entryValue),
value: i,
));
}
}
return Scaffold(
appBar: AppBar(
leading: IconButton(
padding: PassyTheme.appBarButtonPadding,
splashRadius: PassyTheme.appBarButtonSplashRadius,
icon: const Icon(Icons.arrow_back_ios_new_rounded),
onPressed: () => Navigator.pop(context),
),
title: args.title,
centerTitle: true,
),
body: ListView(children: [
PassyPadding(Text(
'${localizations.csvImportMessage1}.\n\n${localizations.csvImportMessage2}.',
textAlign: TextAlign.center,
)),
ListView.builder(
shrinkWrap: true,
itemBuilder: (context, index) {
String entryJsonKey = entryJsonKeys[index];
String name = entryJsonKey;
switch (entryJsonKey) {
case 'additionalInfo':
name = localizations.additionalInfo;
break;
case 'nickname':
name = localizations.nickname;
break;
case 'username':
name = localizations.username;
break;
case 'email':
name = localizations.email;
break;
case 'password':
name = localizations.password;
break;
case 'website':
name = localizations.website;
break;
case 'cardNumber':
name = localizations.cardNumber;
break;
case 'cardholderName':
name = localizations.cardHolderName;
break;
case 'cvv':
name = 'CVV';
break;
case 'exp':
name = localizations.expirationDate;
break;
case 'title':
name = localizations.title;
break;
case 'note':
name = localizations.note;
break;
case 'type':
name = localizations.type;
break;
case 'idNumber':
name = localizations.idNumber;
break;
case 'name':
name = localizations.name;
break;
case 'issDate':
name = localizations.dateOfIssue;
break;
case 'expDate':
name = localizations.expirationDate;
break;
case 'firstName':
name = localizations.firstName;
break;
case 'middleName':
name = localizations.middleName;
break;
case 'lastName':
name = localizations.lastName;
break;
case 'gender':
name = localizations.gender;
break;
case 'number':
name = localizations.phoneNumber;
break;
case 'firstAddressLine':
name = localizations.firstAddresssLine;
break;
case 'secondAddressLine':
name = localizations.secondAddressLine;
break;
case 'zipCode':
name = localizations.zipCode;
break;
case 'city':
name = localizations.city;
break;
case 'country':
name = localizations.country;
break;
}
return PassyPadding(DropdownButtonFormField<int>(
value: -1,
items: items,
onChanged: (value) {
if (value == null) return;
jsonToCSV[entryJsonKey] = value;
},
decoration: InputDecoration(labelText: name),
));
},
itemCount: entryJsonKeys.length,
),
PassyPadding(ThreeWidgetButton(
left: const Padding(
padding: EdgeInsets.only(right: 30),
child: Icon(Icons.download_for_offline_outlined),
),
right: const Icon(Icons.arrow_forward_ios_rounded),
center: Text(localizations.import),
onPressed: () async {
List<PassyEntry> result = [];
DateTime _now = DateTime.now().toUtc();
int i = 0;
for (List<dynamic> entry in args.entries) {
Map<String, dynamic> jsonResult = {
'key': '${_now.toIso8601String()}-import-$i',
};
for (MapEntry<String, int> jsonToCSVEntry
in jsonToCSV.entries) {
int index = jsonToCSVEntry.value;
if (index == -1) {
jsonResult[jsonToCSVEntry.key] = '';
continue;
}
if (index >= entry.length) {
jsonResult[jsonToCSVEntry.key] = '';
continue;
}
dynamic entryValue = entry[index];
if (entryValue is! String) {
jsonResult[jsonToCSVEntry.key] = '';
continue;
}
jsonResult[jsonToCSVEntry.key] = entryValue;
}
PassyEntry entryDecoded;
try {
entryDecoded =
PassyEntry.fromJson(args.entryType)(jsonResult);
} catch (e, s) {
showSnackBar(
context,
message: localizations.couldNotImportAccount,
icon: const Icon(Icons.download_for_offline_outlined,
color: PassyTheme.darkContentColor),
action: SnackBarAction(
label: localizations.details,
onPressed: () => Navigator.pushNamed(
context, LogScreen.routeName,
arguments: e.toString() + '\n' + s.toString()),
),
);
return;
}
result.add(entryDecoded);
i++;
}
Future<void> Function(PassyEntry<dynamic>) setEntry =
data.loadedAccount!.setEntry(args.entryType);
for (PassyEntry entry in result) {
await setEntry(entry);
}
Navigator.pop(context);
})),
]));
}
}

0 comments on commit efb59aa

Please sign in to comment.