main #2

Closed
ryleedavis wants to merge 8 commits from alexvasl/drifter_app:main into main
9 changed files with 95 additions and 642 deletions
Showing only changes of commit d25e5074f6 - Show all commits

View File

@ -1,6 +1,7 @@
import 'package:dart_nostr/dart_nostr.dart';
import 'package:drifter/models/keys.dart';
import 'package:drifter/pages/home_screen/widgets/message_text_button_widget.dart';
import 'package:drifter/pages/profile_screen/profile_screen.dart';
import 'package:drifter/pages/profile_screen/widgets/keys_option_modal_bottom_sheet.dart';
import 'package:drifter/pages/profile_screen/widgets/message_snack_bar.dart';
@ -11,30 +12,16 @@ import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:nostr_tools/nostr_tools.dart';
class LoginScreen extends StatelessWidget {
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
final _secureStorage = const FlutterSecureStorage();
@override
State<LoginScreen> createState() => LoginScreenState();
}
Future<bool> addKeyToStorage(
String privateKeyHex,
String publicKeyHex,
String nsecKey,
String npubKey,
) async {
// Waiting for both write operations to complete
Future.wait([
_secureStorage.write(key: 'privateKey', value: privateKeyHex),
_secureStorage.write(key: 'publicKey', value: privateKeyHex),
_secureStorage.write(key: 'nsec', value: nsecKey),
_secureStorage.write(key: 'npub', value: npubKey),
]);
// Updating status variables and starting widget rebuilding
// Returns a boolean value indicating whether the keys were successfully added to the repository or not.
return Keys.keysExist;
}
class LoginScreenState extends State<LoginScreen> {
final keyGenerator = KeyApi();
final nip19 = Nip19();
@override
Widget build(BuildContext context) {
@ -70,8 +57,9 @@ class LoginScreen extends StatelessWidget {
bool isValidHexKey =
Nostr.instance.keysService.isValidPrivateKey(value);
bool isValidNSec = value.trim().startsWith('nsec') &&
Nostr.instance.keysService.isValidPrivateKey(
NostrClientUtils.hexEncode(value)['data']);
Nostr.instance.keysService.isValidPrivateKey(Nostr
.instance.keysService
.decodeNsecKeyToPrivateKey(value));
if (!(isValidHexKey || isValidNSec)) {
return 'Your private key is not valid.';
}
@ -92,59 +80,68 @@ class LoginScreen extends StatelessWidget {
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(AppColors.background)),
child: const Text(
'Login',
),
onPressed: () {
if (formKey.currentState!.validate()) {
// Он получает значение закрытого ключа из _keyController текстового поля и присваивает его переменной privateKeyHex.
String privateKeyHex = keyController.text.trim();
String publicKeyHex;
String nsecKey;
String npubKey;
// Он проверяет, начинается ли строка privateKeyHex со строки « nsec », что указывает на то, что она может быть в формате NIP-19. Если это так, он декодирует метод privateKeyHex using _nip19.decode(privateKeyHex) для получения поля « данные », которое представляет фактический закрытый ключ в шестнадцатеричном формате.
// if (privateKeyHex.startsWith('nsec')) {
// nsecKey = privateKeyHex;
// final decoded = Nostr.instance.keysService
// .decodeNsecKeyToPrivateKey(privateKeyHex);
// privateKeyHex = decoded;
// publicKeyHex = Nostr.instance.keysService
// .derivePublicKey(privateKey: 'privateKeyHex');
// npubKey = Nostr.instance.keysService
// .encodePublicKeyToNpub(publicKeyHex);
// } else {
// publicKeyHex = Nostr.instance.keysService
// .derivePublicKey(privateKey: 'privateKeyHex');
// nsecKey = Nostr.instance.keysService
// .encodePrivateKeyToNsec(privateKeyHex);
// npubKey = Nostr.instance.keysService
// .encodePublicKeyToNpub(publicKeyHex);
// }
if (privateKeyHex.startsWith('nsec')) {
nsecKey = privateKeyHex;
final decoded = Nostr.instance.keysService
.decodeNsecKeyToPrivateKey(privateKeyHex);
privateKeyHex = decoded;
publicKeyHex = Nostr.instance.keysService
.derivePublicKey(privateKey: 'privateKeyHex');
npubKey = Nostr.instance.keysService
.encodePublicKeyToNpub(publicKeyHex);
}
// Если privateKeyHex не начинается с « nsec », это означает, что это обычный шестнадцатеричный закрытый ключ.
else {
publicKeyHex = Nostr.instance.keysService
.derivePublicKey(privateKey: 'privateKeyHex');
nsecKey = Nostr.instance.keysService
.encodePrivateKeyToNsec(privateKeyHex);
npubKey = Nostr.instance.keysService
.encodePublicKeyToNpub(publicKeyHex);
final decoded = nip19.decode(privateKeyHex);
privateKeyHex = decoded['data'];
publicKeyHex = keyGenerator.getPublicKey(privateKeyHex);
nsecKey = nip19.nsecEncode(privateKeyHex);
npubKey = nip19.npubEncode(publicKeyHex);
} else {
publicKeyHex = keyGenerator.getPublicKey(privateKeyHex);
nsecKey = nip19.nsecEncode(privateKeyHex);
npubKey = nip19.npubEncode(publicKeyHex);
}
// Затем он вызывает _addKeysToStorage метод для добавления закрытого ключа и открытого ключа в хранилище. Он прикрепляет then() к этому методу обратный вызов для обработки случая, когда ключи успешно добавлены в хранилище.
addKeyToStorage(privateKeyHex, publicKeyHex, nsecKey, npubKey)
ProfileScreenState()
.addKeyToStorage(
privateKeyHex, publicKeyHex, nsecKey, npubKey)
.then((keysAdded) {
if (keysAdded) {
keyController.clear();
ScaffoldMessenger.of(context).showSnackBar(
MessageSnackBar(label: 'Congratulations! Keys Stored!'),
);
setState() {
Keys.privateKey = privateKeyHex;
Keys.publicKey = publicKeyHex;
Keys.nsecKey = nsecKey;
Keys.npubKey = npubKey;
Keys.keysExist = true;
}
// setState() {
// Keys.privateKey = privateKeyHex;
// Keys.publicKey = publicKeyHex;
// Keys.nsecKey = nsecKey;
// Keys.npubKey = npubKey;
// Keys.keysExist = true;
// }
}
});
} else {
formKey.currentState?.setState(() {});
}
},
child: Text(
'Login',
),
),
)
],

View File

@ -17,7 +17,8 @@ class ProfileScreen extends StatefulWidget {
}
class ProfileScreenState extends State<ProfileScreen> {
final _secureStorage = const FlutterSecureStorage();
final secureStorage = const FlutterSecureStorage();
bool _toHex = false;
TextEditingController privateKeyInput = TextEditingController();
TextEditingController publicKeyInput = TextEditingController();
@ -37,6 +38,7 @@ class ProfileScreenState extends State<ProfileScreen> {
final nsec =
Nostr.instance.keysService.encodePrivateKeyToNsec(newPrivateKey);
// final nsecDecoded =
// Nostr.instance.keysService.decodeNsecKeyToPrivateKey(nsec);
// assert(nsecDecoded['type'] == 'nsec');
@ -47,6 +49,7 @@ class ProfileScreenState extends State<ProfileScreen> {
// final newPublicKey = keyGenerator.getPublicKey(newPrivateKey);
final npub = Nostr.instance.keysService.encodePublicKeyToNpub(newPublicKey);
// final npubDecoded =
// Nostr.instance.keysService.decodeNpubKeyToPublicKey(npub);
// assert(npubDecoded['type'] == 'npub');
@ -57,11 +60,11 @@ class ProfileScreenState extends State<ProfileScreen> {
Future<void> _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');
final storedPrivateKey = await secureStorage.read(key: 'privateKey');
final storedPublicKey = await secureStorage.read(key: 'publicKey');
final storedNsecKey = await _secureStorage.read(key: 'nsec');
final storedNpubKey = await _secureStorage.read(key: 'npub');
final storedNsecKey = await secureStorage.read(key: 'nsec');
final storedNpubKey = await secureStorage.read(key: 'npub');
// Indicates that both private and public keys are stored in a secure repository, after which, the state variables are updated
if (storedPrivateKey != null &&
@ -88,10 +91,10 @@ class ProfileScreenState extends State<ProfileScreen> {
) async {
// Waiting for both write operations to complete
Future.wait([
_secureStorage.write(key: 'privateKey', value: privateKeyHex),
_secureStorage.write(key: 'publicKey', value: publicKeyHex),
_secureStorage.write(key: 'nsec', value: nsecKey),
_secureStorage.write(key: 'npub', value: npubKey),
secureStorage.write(key: 'privateKey', value: privateKeyHex),
secureStorage.write(key: 'publicKey', value: publicKeyHex),
secureStorage.write(key: 'nsec', value: nsecKey),
secureStorage.write(key: 'npub', value: npubKey),
]);
// Updating status variables and starting widget rebuilding
@ -110,10 +113,10 @@ class ProfileScreenState extends State<ProfileScreen> {
Future<void> _deleteKeysStorage() async {
// Calling secure storage to remove keys from storage
Future.wait([
_secureStorage.delete(key: 'privateKey'),
_secureStorage.delete(key: 'publicKey'),
_secureStorage.delete(key: 'nsec'),
_secureStorage.delete(key: 'npub'),
secureStorage.delete(key: 'privateKey'),
secureStorage.delete(key: 'publicKey'),
secureStorage.delete(key: 'nsec'),
secureStorage.delete(key: 'npub'),
]);
// Updating status variables, resetting values after deleting keys from the repository
@ -128,42 +131,49 @@ class ProfileScreenState extends State<ProfileScreen> {
@override
Widget build(BuildContext context) {
privateKeyInput.text = Keys.nsecKey;
publicKeyInput.text = Keys.npubKey;
privateKeyInput.text = _toHex ? Keys.nsecKey : Keys.privateKey;
publicKeyInput.text = _toHex ? Keys.npubKey : Keys.publicKey;
return ListView(
children: [
SizedBox(
const SizedBox(
height: 60,
),
UserInfo(),
SizedBox(
const UserInfo(),
const SizedBox(
height: 40,
),
FormKeys(),
SizedBox(height: 20),
const 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.background)),
? IconButton(
onPressed: () {
keysExistDialog(
Nostr.instance.keysService
.encodePublicKeyToNpub(Keys.publicKey),
Nostr.instance.keysService
.encodePrivateKeyToNsec(Keys.privateKey),
);
setState(() {
_toHex = !_toHex;
});
},
child: Text(
'Keys',
),
)
icon: const Icon(Icons.refresh))
// ElevatedButton(
// style: ButtonStyle(
// backgroundColor:
// MaterialStateProperty.all(AppColors.background)),
// onPressed: () {
// keysExistDialog(
// Nostr.instance.keysService
// .encodePublicKeyToNpub(Keys.publicKey),
// Nostr.instance.keysService
// .encodePrivateKeyToNsec(Keys.privateKey),
// );
// },
// child: const Text(
// 'Keys',
// ),
// )
: ElevatedButton(
style: ButtonStyle(
backgroundColor:
@ -171,7 +181,7 @@ class ProfileScreenState extends State<ProfileScreen> {
onPressed: () {
modalBottomSheet();
},
child: Text(
child: const Text(
'Generate Keys',
),
),

View File

@ -1,87 +0,0 @@
import 'package:flutter/material.dart';
import 'package:drifter/theme/app_colors.dart';
import 'ok_button_widget.dart';
class DeleteKeysDialog extends StatelessWidget {
const DeleteKeysDialog({
super.key,
required this.onNoPressed,
required this.onYesPressed,
});
final void Function()? onNoPressed;
final void Function()? onYesPressed;
@override
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Container(
constraints: const BoxConstraints(maxWidth: 600),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.symmetric(vertical: 24),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.redAccent,
),
child: const Center(
child: Text(
'Delete Keys!',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
const SizedBox(height: 24),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Do you want to delete your keys?',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.grey[700],
),
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: onNoPressed,
child: Text(
'On',
style: TextStyle(color: AppColors.mainDarkBlue),
),
),
OkButton(
onPressed: onYesPressed,
label: 'YES',
),
],
),
],
),
)
],
),
),
);
}
}

View File

@ -1,128 +0,0 @@
import 'package:flutter/material.dart';
import 'ok_button_widget.dart';
class GeneratedKeys extends StatefulWidget {
const GeneratedKeys({
super.key,
required this.npubEncoded,
required this.nsecEncoded,
required this.hexPriv,
required this.hexPub,
});
final String npubEncoded;
final String nsecEncoded;
final String hexPriv;
final String hexPub;
@override
State<GeneratedKeys> createState() => _GeneratedKeysState();
}
class _GeneratedKeysState extends State<GeneratedKeys> {
bool _toHex = false;
@override
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Container(
constraints: const BoxConstraints(maxWidth: 600),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.symmetric(vertical: 24),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.indigo,
),
child: const Center(
child: Text(
'Keys',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
const SizedBox(height: 24),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Public Key',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.grey[700],
),
),
const SizedBox(height: 12),
SelectableText(
_toHex ? widget.hexPub : widget.npubEncoded,
style: TextStyle(
fontSize: 16,
color: Colors.grey[800],
),
),
const SizedBox(height: 24),
Text(
'Private Key',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.grey[700],
),
),
const SizedBox(height: 12),
SelectableText(
_toHex ? widget.hexPriv : widget.nsecEncoded,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.redAccent,
),
),
const SizedBox(height: 24),
Row(
mainAxisAlignment: MainAxisAlignment
.spaceBetween, // Changed to space between to create space for icon buttons
children: [
IconButton(
onPressed: () {
setState(() {
_toHex = !_toHex;
});
},
icon: const Icon(Icons.autorenew_outlined),
color: Colors.grey[700],
),
OkButton(
onPressed: () {
Navigator.pop(context);
},
label: 'OK',
),
],
)
],
),
),
],
),
),
);
}
}

View File

@ -1,128 +0,0 @@
import 'package:flutter/material.dart';
import 'package:drifter/theme/app_colors.dart';
import 'ok_button_widget.dart';
class KeysExistDialog extends StatefulWidget {
const KeysExistDialog({
super.key,
required this.npubEncoded,
required this.nsecEncoded,
required this.hexPriv,
required this.hexPub,
});
final String npubEncoded;
final String nsecEncoded;
final String hexPriv;
final String hexPub;
@override
State<KeysExistDialog> createState() => _KeysExistDialogState();
}
class _KeysExistDialogState extends State<KeysExistDialog> {
bool _toHex = false;
@override
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Container(
constraints: const BoxConstraints(maxWidth: 600),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Colors.white,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.symmetric(vertical: 24),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: AppColors.mainDarkBlue,
),
child: const Center(
child: Text(
'Keys',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
const SizedBox(height: 24),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Public Key',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.grey[700],
),
),
const SizedBox(height: 12),
SelectableText(
_toHex ? widget.hexPub : widget.npubEncoded,
style: TextStyle(
fontSize: 16,
color: Colors.grey[800],
),
),
const SizedBox(height: 24),
Text(
'Private Key',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.grey[700],
),
),
const SizedBox(height: 12),
SelectableText(
_toHex ? widget.hexPriv : widget.nsecEncoded,
style: TextStyle(
fontSize: 16,
color: Colors.grey[800],
),
),
const SizedBox(height: 24),
Row(
mainAxisAlignment: MainAxisAlignment
.spaceBetween, // Changed to space between to create space for icon buttons
children: [
IconButton(
onPressed: () {
setState(() {
_toHex = !_toHex;
});
},
icon: const Icon(Icons.autorenew_outlined),
color: Colors.grey[700],
),
OkButton(
onPressed: () {
Navigator.pop(context);
},
label: 'OK',
),
],
)
],
),
),
],
),
),
);
}
}

View File

@ -1,34 +0,0 @@
import 'package:flutter/material.dart';
import 'package:drifter/theme/app_colors.dart';
class KeysOptionModalBottomSheet extends StatelessWidget {
const KeysOptionModalBottomSheet({
super.key,
required this.generateNewKeyPressed,
});
final void Function()? generateNewKeyPressed;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(color: AppColors.mainDarkBlue),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.white)),
onPressed: generateNewKeyPressed,
child: Text(
'Generate New Key',
style: TextStyle(color: AppColors.mainDarkBlue),
),
),
const SizedBox(height: 10),
],
),
);
}
}

View File

@ -1,46 +0,0 @@
import 'package:flutter/material.dart';
class MessageSnackBar extends SnackBar {
MessageSnackBar({Key? key, required this.label, this.isWarning = false})
: super(
key: key,
content: _GenericErrorSnackBarMessage(
label: label,
isWarning: isWarning,
),
backgroundColor: isWarning! ? Colors.red : Colors.white,
elevation: 6.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
behavior: SnackBarBehavior.fixed,
);
final String label;
final bool? isWarning;
}
class _GenericErrorSnackBarMessage extends StatelessWidget {
const _GenericErrorSnackBarMessage({
Key? key,
required this.label,
this.isWarning,
}) : super(key: key);
final String label;
final bool? isWarning;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
child: Text(
label,
style: TextStyle(
color: isWarning! ? Colors.white : Colors.black,
fontSize: 16.0,
),
),
);
}
}

View File

@ -1,34 +0,0 @@
import 'package:flutter/material.dart';
import 'package:drifter/theme/app_colors.dart';
class OkButton extends StatelessWidget {
const OkButton({
super.key,
required this.onPressed,
required this.label,
});
final void Function()? onPressed;
final String label;
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.mainDarkBlue,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text(
label,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
);
}
}

View File

@ -1,97 +0,0 @@
import 'package:drifter/theme/app_colors.dart';
import 'package:flutter/material.dart';
class UserInfo extends StatelessWidget {
const UserInfo({super.key});
@override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
AvatarWidget(),
SizedBox(
height: 10,
),
UserNameWidget(),
],
),
);
}
}
class AvatarWidget extends StatelessWidget {
const AvatarWidget({super.key});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.black),
borderRadius: BorderRadius.all(Radius.circular(75))),
width: 150,
height: 150,
);
}
}
class UserNameWidget extends StatefulWidget {
const UserNameWidget({super.key});
@override
State<UserNameWidget> createState() => _UserNameWidgetState();
}
class _UserNameWidgetState extends State<UserNameWidget> {
late final TextEditingController messageController;
late final FocusNode messageFocusNode;
@override
void initState() {
messageController = TextEditingController();
messageFocusNode = FocusNode();
super.initState();
}
@override
void dispose() {
messageController.dispose();
messageFocusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(4.0),
child: ClipRRect(
borderRadius: const BorderRadius.all(
Radius.circular(
0.0,
),
),
child: TextField(
controller: messageController,
focusNode: messageFocusNode,
style: const TextStyle(
fontSize: 14,
),
decoration: InputDecoration(
hintText: 'Username',
hintStyle: const TextStyle(fontSize: 14),
suffixIcon: IconButton(
icon: const Icon(
Icons.send,
color: AppColors.mainDarkBlue,
size: 30,
),
onPressed: () {},
),
),
),
),
);
}
}