import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:drifter/models/keys.dart'; import 'package:drifter/theme/app_colors.dart'; import 'package:nostr_tools/nostr_tools.dart'; import 'profile_screen_widgets/delete_keys_dialog.dart'; import 'profile_screen_widgets/key_exist_dialog.dart'; import 'profile_screen_widgets/keys_option_modal_bottom_sheet.dart'; import 'profile_screen_widgets/message_snack_bar.dart'; import 'profile_screen_widgets/user_info_widget.dart'; class ProfileScreen extends StatefulWidget { const ProfileScreen({super.key}); @override State createState() => ProfileScreenState(); } class ProfileScreenState extends State { final _secureStorage = const FlutterSecureStorage(); TextEditingController privateKeyInput = TextEditingController(); TextEditingController publicKeyInput = TextEditingController(); TextEditingController relayInput = TextEditingController(); final keyGenerator = KeyApi(); final nip19 = Nip19(); void initState() { _getKeysFromStorage(); super.initState(); } Future generateNewKeys() async { final newPrivateKey = keyGenerator.generatePrivateKey(); final nsec = nip19.nsecEncode(newPrivateKey); final nsecDecoded = nip19.decode(nsec); assert(nsecDecoded['type'] == 'nsec'); assert(nsecDecoded['data'] == newPrivateKey); final newPublicKey = keyGenerator.getPublicKey(newPrivateKey); final npub = nip19.npubEncode(newPublicKey); final npubDecoded = nip19.decode(npub); assert(npubDecoded['type'] == 'npub'); assert(npubDecoded['data'] == newPublicKey); return await _addKeyToStorage(nsec, npub); } Future _getKeysFromStorage() async { // Reading values associated with the " privateKey " and " publicKey " keys from a secure repository final storedPrivateKey = await _secureStorage.read(key: 'privateKey'); final storedPublicKey = await _secureStorage.read(key: 'publicKey'); // Indicates that both private and public keys are stored in a secure repository, after which, the state variables are updated if (storedPrivateKey != null && storedPublicKey != null) { setState(() { Keys.privateKey = storedPrivateKey; Keys.publicKey = storedPublicKey; Keys.keysExist = true; }); } } // Adding a new key // Writing a private and public key to a secure vault Future _addKeyToStorage( String privateKeyHex, String publicKeyHex, ) async { // Waiting for both write operations to complete Future.wait([ _secureStorage.write(key: 'privateKey', value: privateKeyHex), _secureStorage.write(key: 'publicKey', value: privateKeyHex), ]); // Updating status variables and starting widget rebuilding setState(() { Keys.privateKey = privateKeyHex; Keys.publicKey = publicKeyHex; Keys.keysExist = true; }); // Returns a boolean value indicating whether the keys were successfully added to the repository or not. return Keys.keysExist; } Future _deleteKeysStorage() async { // Calling secure storage to remove keys from storage Future.wait([ _secureStorage.delete(key: 'privateKey'), _secureStorage.delete(key: 'publicKey'), ]); // Updating status variables, resetting values after deleting keys from the repository setState(() { Keys.privateKey = ''; Keys.publicKey = ''; Keys.keysExist = false; }); } @override Widget build(BuildContext context) { privateKeyInput.text = Keys.privateKey; publicKeyInput.text = Keys.publicKey; relayInput.text = Relay.relay.relayUrl; return ListView( children: [ SizedBox( height: 60, ), UserInfo(), SizedBox( height: 40, ), FormKeys(), SizedBox(height: 20), Padding( padding: const EdgeInsets.all(16.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Keys.keysExist ? ElevatedButton( style: ButtonStyle( backgroundColor: MaterialStateProperty.all( AppColors.mainDarkBlue)), onPressed: () { keysExistDialog( nip19.npubEncode(Keys.publicKey), nip19.nsecEncode(Keys.privateKey), ); }, child: Text( 'Keys', ), ) : ElevatedButton( style: ButtonStyle( backgroundColor: MaterialStateProperty.all( AppColors.mainDarkBlue)), onPressed: () { modalBottomSheet(); }, child: Text( 'Generate Keys', ), ), Keys.keysExist ? Row( children: [ IconButton( onPressed: () { deleteKeysDialog(); }, icon: const Icon(Icons.delete)), ], ) : Container(), ], ), ) ], ); } Form FormKeys() { return Form( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ TextFormField( controller: privateKeyInput, // _toHex ? widget.hexPriv : widget.nsecEncoded, decoration: const InputDecoration( labelText: 'Private Key', border: OutlineInputBorder(), ), ), const SizedBox( height: 20, ), TextFormField( controller: publicKeyInput, // _toHex ? widget.hexPub : widget.npubEncoded, decoration: const InputDecoration( labelText: 'Public Key', border: OutlineInputBorder(), ), ), const SizedBox( height: 40, ), TextFormField( controller: relayInput, decoration: const InputDecoration( labelText: 'Relay', border: OutlineInputBorder(), ), ), ], ), ), ); } void modalBottomSheet() { showModalBottomSheet( context: context, builder: (BuildContext context) { return KeysOptionModalBottomSheet( generateNewKeyPressed: () { final currentContext = context; generateNewKeys().then( (keysGenerated) { if (keysGenerated) { ScaffoldMessenger.of(currentContext).showSnackBar( MessageSnackBar(label: 'Keys Generated!')); } }, ); Navigator.pop(context); }, ); }); } void keysExistDialog(String npubEncode, String nsecEncode) async { await showDialog( context: context, builder: ((context) { return KeysExistDialog( npubEncoded: npubEncode, nsecEncoded: nsecEncode, hexPriv: Keys.privateKey, hexPub: Keys.publicKey, ); }), ); } void deleteKeysDialog() async { await showDialog( context: context, builder: ((context) { return DeleteKeysDialog( onNoPressed: () { Navigator.pop(context); }, onYesPressed: () { final currentContext = context; _deleteKeysStorage().then((_) { if (!Keys.keysExist) { ScaffoldMessenger.of(currentContext).showSnackBar( MessageSnackBar( label: 'Keys successfully deleted!', isWarning: true, ), ); } }); Navigator.pop(context); }, ); }), ); } }