diff --git a/lib/onboarding/view/setup_enter_secret_key_page.dart b/lib/onboarding/view/setup_enter_secret_key_page.dart index 20efa0fbe..a98330054 100644 --- a/lib/onboarding/view/setup_enter_secret_key_page.dart +++ b/lib/onboarding/view/setup_enter_secret_key_page.dart @@ -1,11 +1,14 @@ import "package:ente_auth/l10n/l10n.dart"; import 'package:ente_auth/models/code.dart'; +// ignore: import_of_legacy_library_into_null_safe import 'package:ente_auth/utils/dialog_util.dart'; import 'package:ente_auth/utils/totp_util.dart'; import "package:flutter/material.dart"; class SetupEnterSecretKeyPage extends StatefulWidget { - SetupEnterSecretKeyPage({Key? key}) : super(key: key); + final Code? code; + + SetupEnterSecretKeyPage({this.code, Key? key}) : super(key: key); @override State createState() => @@ -13,8 +16,21 @@ class SetupEnterSecretKeyPage extends StatefulWidget { } class _SetupEnterSecretKeyPageState extends State { - final _accountController = TextEditingController(); - final _secretController = TextEditingController(text: ""); + late TextEditingController _accountController; + late TextEditingController _secretController; + + @override + void initState() { + _accountController = TextEditingController( + text: widget.code != null + ? Uri.decodeFull(widget.code!.account).toString() + : null, + ); + _secretController = TextEditingController( + text: widget.code != null ? widget.code!.secret : null, + ); + super.initState(); + } @override Widget build(BuildContext context) { @@ -78,6 +94,9 @@ class _SetupEnterSecretKeyPageState extends State { ); // Verify the validity of the code getTotp(code); + if (widget.code != null) { + code.id = widget.code!.id; + } Navigator.of(context).pop(code); } catch (e) { _showIncorrectDetailsDialog(context); diff --git a/lib/services/authenticator_service.dart b/lib/services/authenticator_service.dart index 491d1c6ab..9c0900cd8 100644 --- a/lib/services/authenticator_service.dart +++ b/lib/services/authenticator_service.dart @@ -88,7 +88,11 @@ class AuthenticatorService { return insertedID; } - Future updateEntry(int generatedID, String plainText) async { + Future updateEntry( + int generatedID, + String plainText, + bool shouldSync, + ) async { var key = await getOrCreateAuthDataKey(); final encryptedKeyData = await CryptoUtil.encryptChaCha( utf8.encode(plainText) as Uint8List, @@ -102,7 +106,9 @@ class AuthenticatorService { affectedRows == 1, "updateEntry should have updated exactly one row", ); - unawaited(sync()); + if (shouldSync) { + unawaited(sync()); + } } Future deleteEntry(int genID) async { diff --git a/lib/store/code_store.dart b/lib/store/code_store.dart index 30faaab7e..562ff5cdd 100644 --- a/lib/store/code_store.dart +++ b/lib/store/code_store.dart @@ -41,16 +41,28 @@ class CodeStore { bool shouldSync = true, }) async { final codes = await getAllCodes(); + bool isExistingCode = false; for (final existingCode in codes) { if (existingCode == code) { _logger.info("Found duplicate code, skipping add"); return; + } else if (existingCode.id == code.id) { + isExistingCode = true; + break; } } - code.id = await _authenticatorService.addEntry( - jsonEncode(code.rawData), - shouldSync, - ); + if (isExistingCode) { + await _authenticatorService.updateEntry( + code.id!, + jsonEncode(code.rawData), + shouldSync, + ); + } else { + code.id = await _authenticatorService.addEntry( + jsonEncode(code.rawData), + shouldSync, + ); + } Bus.instance.fire(CodesUpdatedEvent()); } diff --git a/lib/ui/code_widget.dart b/lib/ui/code_widget.dart index 236ded0a5..3a5ec7314 100644 --- a/lib/ui/code_widget.dart +++ b/lib/ui/code_widget.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:clipboard/clipboard.dart'; import 'package:ente_auth/ente_theme_data.dart'; import 'package:ente_auth/models/code.dart'; +import 'package:ente_auth/onboarding/view/setup_enter_secret_key_page.dart'; import 'package:ente_auth/store/code_store.dart'; import 'package:ente_auth/utils/toast_util.dart'; import 'package:ente_auth/utils/totp_util.dart'; @@ -55,6 +56,20 @@ class _CodeWidgetState extends State { endActionPane: ActionPane( motion: const ScrollMotion(), children: [ + SlidableAction( + onPressed: _onEditPressed, + backgroundColor: Colors.grey.withOpacity(0.1), + borderRadius: const BorderRadius.all(Radius.circular(12.0)), + foregroundColor: + Theme.of(context).colorScheme.inverseBackgroundColor, + icon: Icons.edit_outlined, + label: 'Edit', + padding: const EdgeInsets.only(left: 4, right: 0), + spacing: 8, + ), + const SizedBox( + width: 4, + ), SlidableAction( onPressed: _onDeletePressed, backgroundColor: Colors.grey.withOpacity(0.1), @@ -63,6 +78,7 @@ class _CodeWidgetState extends State { icon: Icons.delete, label: 'Delete', padding: const EdgeInsets.only(left: 0, right: 0), + spacing: 8, ), ], ), @@ -194,6 +210,19 @@ class _CodeWidgetState extends State { ); } + Future _onEditPressed(_) async { + final Code? code = await Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) { + return SetupEnterSecretKeyPage(code: widget.code); + }, + ), + ); + if (code != null) { + CodeStore.instance.addCode(code); + } + } + void _onDeletePressed(_) { final AlertDialog alert = AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),