Displaying keys as npub and nsec
This commit is contained in:
parent
5b5cf93674
commit
560c4cffc9
@ -1,4 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
String avatarUrl = "https://sweary.com/avatar-generator/";
|
||||
String imageSize = "size=200x200";
|
@ -1,21 +0,0 @@
|
||||
class Domain {
|
||||
final String noteId;
|
||||
final String avatarUrl;
|
||||
final String name;
|
||||
final String username;
|
||||
final String time;
|
||||
final String content;
|
||||
final String pubkey;
|
||||
final String? imageUrl;
|
||||
|
||||
Domain({
|
||||
required this.noteId,
|
||||
required this.avatarUrl,
|
||||
required this.name,
|
||||
required this.username,
|
||||
required this.time,
|
||||
required this.content,
|
||||
required this.pubkey,
|
||||
this.imageUrl,
|
||||
});
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:drifter/theme/app_colors.dart';
|
||||
|
||||
import 'package:drifter/widgets/main_screen/main_screen_widget.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
theme: ThemeData(
|
||||
appBarTheme: const AppBarTheme(backgroundColor: AppColors.mainDarkBlue),
|
||||
primarySwatch: Colors.blue,
|
||||
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
|
||||
backgroundColor: AppColors.mainDarkBlue,
|
||||
selectedItemColor: Colors.white,
|
||||
unselectedItemColor: Colors.grey,
|
||||
),
|
||||
),
|
||||
home: const MainScreenWidget(),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:nostr_tools/nostr_tools.dart';
|
||||
|
||||
class Keys {
|
||||
static String privateKey = '';
|
||||
static String publicKey = '';
|
||||
static bool keysExist = false;
|
||||
}
|
||||
|
||||
class Relay {
|
||||
static final relay = RelayApi(relayUrl: 'wss://relay.damus.io');
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract class AppColors {
|
||||
static const mainDarkBlue = Color.fromRGBO(3, 37, 65, 1);
|
||||
static const mainLightBlue = Color.fromRGBO(48, 86, 117, 1);
|
||||
}
|
@ -1,442 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:drifter/domain_models/domain_models.dart';
|
||||
import 'package:drifter/models/keys.dart';
|
||||
import 'package:drifter/theme/app_colors.dart';
|
||||
import 'package:drifter/widgets/home_screen/home_screen_widgets/message_ok_button_widget.dart';
|
||||
import 'package:drifter/widgets/home_screen/home_screen_widgets/message_text_button_widget.dart';
|
||||
import 'package:drifter/widgets/home_screen/home_screen_widgets/message_text_form_field_widget.dart';
|
||||
import 'package:drifter/widgets/profile_screen/profile_screen_widgets/message_snack_bar.dart';
|
||||
import 'package:nostr_tools/nostr_tools.dart';
|
||||
|
||||
import '../profile_screen/profile_screen.dart';
|
||||
|
||||
class HomeScreen extends StatefulWidget {
|
||||
const HomeScreen({super.key});
|
||||
|
||||
@override
|
||||
State<HomeScreen> createState() => _HomeScreenState();
|
||||
}
|
||||
|
||||
class _HomeScreenState extends State<HomeScreen> {
|
||||
bool _isConnected = false;
|
||||
|
||||
final List<Event> _events = [];
|
||||
final Map<String, Metadata> _metaDatas = {};
|
||||
late Stream<Event> _stream;
|
||||
final _controller = StreamController<Event>();
|
||||
|
||||
bool _isNotePublishing = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_initStream();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Future<Stream<Event>> _connectToRelay() async {
|
||||
final stream = await Relay.relay.connect();
|
||||
|
||||
Relay.relay.on((event) {
|
||||
if (event == RelayEvent.connect) {
|
||||
setState(() => _isConnected = true);
|
||||
} else if (event == RelayEvent.error) {
|
||||
setState(() => _isConnected = false);
|
||||
}
|
||||
});
|
||||
|
||||
Relay.relay.sub([
|
||||
Filter(
|
||||
kinds: [1],
|
||||
limit: 100,
|
||||
t: ['nostr'],
|
||||
)
|
||||
]);
|
||||
|
||||
return stream
|
||||
.where((message) => message.type == 'EVENT')
|
||||
.map((message) => message.message);
|
||||
}
|
||||
|
||||
void _initStream() async {
|
||||
_stream = await _connectToRelay();
|
||||
_stream.listen((message) {
|
||||
final event = message;
|
||||
if (event.kind == 1) {
|
||||
setState(() => _events.add(event));
|
||||
Relay.relay.sub([
|
||||
Filter(kinds: [0], authors: [event.pubkey])
|
||||
]);
|
||||
} else if (event.kind == 0) {
|
||||
final metadata = Metadata.fromJson(jsonDecode(event.content));
|
||||
setState(() => _metaDatas[event.pubkey] = metadata);
|
||||
}
|
||||
_controller.add(event);
|
||||
});
|
||||
}
|
||||
|
||||
// The _resubscribeStream() method clears the _events and _metaData scollection after a 1 second delay
|
||||
Future<void> _resubscribeStream() async {
|
||||
await Future.delayed(const Duration(seconds: 1), () {
|
||||
setState(() {
|
||||
_events.clear();
|
||||
_metaDatas.clear();
|
||||
});
|
||||
// _initStream() method, responsible for initializing and subscribing to the stream, to reconnect and re-subscribe to the filter.
|
||||
_initStream();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await _resubscribeStream();
|
||||
},
|
||||
child: StreamBuilder(
|
||||
stream: _controller.stream,
|
||||
builder: (context, snapshot) {
|
||||
// Inside the builder callback, the snapshot object contains the most recent event from the thread.
|
||||
// If snapshot.hasData is true, there is data to display. In this case, ListView.builder is returned, which displays a list of NoostCard widgets.
|
||||
if (snapshot.hasData) {
|
||||
return ListView.builder(
|
||||
// The itemCount property of ListView.builder is set to _events.length, , which is the number of events in the _events list.
|
||||
itemCount: _events.length,
|
||||
itemBuilder: (context, index) {
|
||||
final event = _events[index];
|
||||
final metadata = _metaDatas[event.pubkey];
|
||||
// For each event, a Noost object is created that encapsulates the details of the event, including id, avatarUrl, name,username, time, content и pubkey.
|
||||
// _metaDatas, you can map the event public key to the author's metadata.
|
||||
final domain = Domain(
|
||||
noteId: event.id,
|
||||
avatarUrl: metadata?.picture ??
|
||||
'https://robohash.org/${event.pubkey}',
|
||||
name: metadata?.name ?? 'Anon',
|
||||
username: metadata?.displayName ??
|
||||
(metadata?.display_name ?? 'Anon'),
|
||||
time: TimeAgo.format(event.created_at),
|
||||
content: event.content,
|
||||
pubkey: event.pubkey,
|
||||
);
|
||||
return DomainCard(domain: domain);
|
||||
},
|
||||
);
|
||||
} else if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: Text('Loading....'));
|
||||
} else if (snapshot.hasError) {
|
||||
return Center(child: Text('Error: ${snapshot.error}'));
|
||||
}
|
||||
return const CenteredCircularProgressIndicator();
|
||||
},
|
||||
),
|
||||
),
|
||||
floatingActionButton: Keys.keysExist
|
||||
? CreatePost(
|
||||
// The publishNote function is called when the user launches the "Noost!" button in the dialog box.
|
||||
publishNote: (note) {
|
||||
setState(() => _isNotePublishing = true);
|
||||
|
||||
// EventApi Creates an instance of the class defined in the nostr_tools package.
|
||||
final eventApi = EventApi();
|
||||
// The finishEvent method of the EventApi class is called with the Event object and the _privateKey variable.
|
||||
// finishEvent will set the event id with the event hash and sign the event with the given _privateKey.
|
||||
final event = eventApi.finishEvent(
|
||||
// This creates a new instance of the Event class with certain properties, such as:
|
||||
Event(
|
||||
kind: 1,
|
||||
tags: [
|
||||
['t', 'nostr']
|
||||
],
|
||||
content: note!,
|
||||
created_at: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
),
|
||||
Keys.publicKey,
|
||||
);
|
||||
if (eventApi.verifySignature(event)) {
|
||||
try {
|
||||
// If the signature is verified, the _relay method is called for the object to publish the event.
|
||||
Relay.relay.publish(event);
|
||||
// After the _resubscribeStream event is published, a method is called that will probably update the stream or subscription to reflect the recently published event.
|
||||
_resubscribeStream();
|
||||
// Show SnackBar to display a message that the note has been successfully published.
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
MessageSnackBar(
|
||||
label: 'Congratulations! Noost Published!'),
|
||||
);
|
||||
} catch (_) {
|
||||
// If an error occurs during the publishing process (e.g., an exception is caught), SnackBar displays a warning instead.
|
||||
ScaffoldMessenger.of(context).showSnackBar(MessageSnackBar(
|
||||
label: 'Oops! Something went wrong!',
|
||||
isWarning: true,
|
||||
));
|
||||
}
|
||||
}
|
||||
setState(() => _isNotePublishing = false);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
isNotePublishing: _isNotePublishing,
|
||||
)
|
||||
:
|
||||
// If _keysExist is false, then an empty widget is displayed, which means that the FAB will not be visible. Container()
|
||||
Container(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TimeAgo {
|
||||
static String format(int timestamp) {
|
||||
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
|
||||
Duration difference = DateTime.now().difference(dateTime);
|
||||
|
||||
String timeAgo = '';
|
||||
|
||||
if (difference.inDays > 0) {
|
||||
timeAgo =
|
||||
'${difference.inDays} ${difference.inDays == 1 ? 'day' : 'days'} ago';
|
||||
} else if (difference.inHours > 0) {
|
||||
timeAgo =
|
||||
'${difference.inHours} ${difference.inHours == 1 ? 'hour' : 'hours'} ago';
|
||||
} else if (difference.inMinutes > 0) {
|
||||
timeAgo =
|
||||
'${difference.inMinutes} ${difference.inMinutes == 1 ? 'minute' : 'minutes'} ago';
|
||||
} else {
|
||||
timeAgo = 'just now';
|
||||
}
|
||||
|
||||
return timeAgo;
|
||||
}
|
||||
}
|
||||
|
||||
class DomainCard extends StatelessWidget {
|
||||
const DomainCard({
|
||||
super.key,
|
||||
required this.domain,
|
||||
});
|
||||
|
||||
final Domain domain;
|
||||
|
||||
List<String>? extractImage(String text) {
|
||||
final RegExp exp = RegExp(
|
||||
r"(http(s?):)([/|.|\w|\s|-])*\.(?:jpg|gif|png|jpeg)",
|
||||
caseSensitive: false,
|
||||
multiLine: true,
|
||||
);
|
||||
|
||||
final Iterable<Match> matches = exp.allMatches(text);
|
||||
|
||||
final List<String> imageLinks =
|
||||
matches.map((match) => match.group(0)!).toList();
|
||||
|
||||
return imageLinks.isNotEmpty ? imageLinks : null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<String>? imageLinks = extractImage(domain.content);
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.mainLightBlue,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.5),
|
||||
spreadRadius: 2,
|
||||
blurRadius: 5,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundImage: FadeInImage(
|
||||
placeholder:
|
||||
const NetworkImage('https://i.ibb.co/mJkxDkb/satoshi.png'),
|
||||
image: NetworkImage(domain.avatarUrl),
|
||||
).image,
|
||||
),
|
||||
title:
|
||||
Text(domain.name, style: const TextStyle(color: Colors.white)),
|
||||
subtitle: Text('@${domain.username.toLowerCase()} • ${domain.time}',
|
||||
style: TextStyle(color: Colors.grey.shade400)),
|
||||
trailing: const Icon(Icons.more_vert, color: Colors.grey),
|
||||
),
|
||||
Divider(height: 1, color: Colors.grey.shade400),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Text(domain.content,
|
||||
style: const TextStyle(color: Colors.white)),
|
||||
),
|
||||
if (imageLinks != null && imageLinks.isNotEmpty)
|
||||
Center(
|
||||
child: Stack(
|
||||
children: [
|
||||
const Placeholder(
|
||||
fallbackHeight: 200,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
Center(
|
||||
child: FadeInImage(
|
||||
placeholder: const NetworkImage(
|
||||
'https://i.ibb.co/D9jqXgR/58038897-167f0280-7ae6-11e9-94eb-88e880a25f0f.gif',
|
||||
),
|
||||
image: NetworkImage(imageLinks.first),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CenteredCircularProgressIndicator extends StatelessWidget {
|
||||
const CenteredCircularProgressIndicator({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CreatePost extends StatefulWidget {
|
||||
const CreatePost({
|
||||
Key? key,
|
||||
required this.publishNote,
|
||||
required this.isNotePublishing,
|
||||
}) : super(key: key);
|
||||
|
||||
final Function(String?) publishNote;
|
||||
final bool isNotePublishing;
|
||||
|
||||
@override
|
||||
State<CreatePost> createState() => _CreatePostState();
|
||||
}
|
||||
|
||||
class _CreatePostState extends State<CreatePost> {
|
||||
final _noteController = TextEditingController();
|
||||
final GlobalKey<FormFieldState> _formKey = GlobalKey<FormFieldState>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FloatingActionButton(
|
||||
backgroundColor: Colors.deepPurpleAccent,
|
||||
tooltip: 'Create a new post',
|
||||
elevation: 2,
|
||||
highlightElevation: 4,
|
||||
foregroundColor: Colors.white,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle, color: AppColors.mainDarkBlue),
|
||||
),
|
||||
const Icon(
|
||||
Icons.add,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
onPressed: () async {
|
||||
_noteController.clear();
|
||||
await showDialog(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: ((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(
|
||||
'Create a Noost',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: MessageTextFormField(
|
||||
maxLines: 5,
|
||||
hintText: 'Type your Noost here...',
|
||||
controller: _noteController,
|
||||
formKey: _formKey,
|
||||
validator: (value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return 'Please enter your note.';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
widget.isNotePublishing
|
||||
? const CenteredCircularProgressIndicator()
|
||||
: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
MessageTextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
label: 'Cancel',
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
MessageOkButton(
|
||||
onPressed: () {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
widget.publishNote(
|
||||
_noteController.text.trim());
|
||||
} else {
|
||||
_formKey.currentState?.setState(() {});
|
||||
}
|
||||
},
|
||||
label: 'Noost!',
|
||||
),
|
||||
const SizedBox(width: 24),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:drifter/theme/app_colors.dart';
|
||||
|
||||
class MessageOkButton extends StatelessWidget {
|
||||
const MessageOkButton({
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:drifter/theme/app_colors.dart';
|
||||
|
||||
class MessageTextButton extends StatelessWidget {
|
||||
const MessageTextButton({
|
||||
super.key,
|
||||
required this.onPressed,
|
||||
required this.label,
|
||||
this.color,
|
||||
});
|
||||
|
||||
final void Function()? onPressed;
|
||||
final String label;
|
||||
final Color? color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextButton(
|
||||
onPressed: onPressed,
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: AppColors.mainDarkBlue,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:drifter/theme/app_colors.dart';
|
||||
|
||||
class MessageTextFormField extends StatelessWidget {
|
||||
const MessageTextFormField({
|
||||
super.key,
|
||||
required this.hintText,
|
||||
required this.controller,
|
||||
required this.formKey,
|
||||
required this.validator,
|
||||
this.maxLines,
|
||||
});
|
||||
|
||||
final String hintText;
|
||||
final TextEditingController controller;
|
||||
final Key formKey;
|
||||
final String? Function(String?)? validator;
|
||||
final int? maxLines;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextFormField(
|
||||
controller: controller,
|
||||
key: formKey,
|
||||
validator: validator,
|
||||
maxLines: maxLines,
|
||||
style: const TextStyle(color: Colors.black),
|
||||
decoration: InputDecoration(
|
||||
hintText: hintText,
|
||||
hintStyle: const TextStyle(color: Colors.black54),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: const BorderSide(color: Colors.black54),
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: const BorderSide(color: Colors.black54),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: const BorderSide(color: AppColors.mainDarkBlue),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:drifter/widgets/home_screen/home_screen_widget.dart';
|
||||
import 'package:drifter/widgets/message_screen/message_screen_widget.dart';
|
||||
import 'package:drifter/widgets/profile_screen/profile_screen.dart';
|
||||
|
||||
class MainScreenWidget extends StatefulWidget {
|
||||
const MainScreenWidget({super.key});
|
||||
|
||||
@override
|
||||
State<MainScreenWidget> createState() => _MainScreenWidgetState();
|
||||
}
|
||||
|
||||
class _MainScreenWidgetState extends State<MainScreenWidget> {
|
||||
int _selectedTap = 0;
|
||||
|
||||
void onSelectedtap(int index) {
|
||||
if (_selectedTap == index) return;
|
||||
setState(() {
|
||||
_selectedTap = index;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Drifter'),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: IndexedStack(
|
||||
index: _selectedTap,
|
||||
children: [
|
||||
HomeScreen(),
|
||||
// MessageScreen(),
|
||||
ProfileScreen(),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
currentIndex: _selectedTap,
|
||||
items: [
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.home),
|
||||
label: 'Home',
|
||||
),
|
||||
// BottomNavigationBarItem(
|
||||
// icon: Icon(Icons.message),
|
||||
// label: 'Message',
|
||||
// ),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.person),
|
||||
label: 'Profile',
|
||||
),
|
||||
],
|
||||
onTap: onSelectedtap,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:drifter/theme/app_colors.dart';
|
||||
|
||||
class MessageScreen extends StatefulWidget {
|
||||
const MessageScreen({super.key});
|
||||
|
||||
@override
|
||||
State<MessageScreen> createState() => _MessageScreenState();
|
||||
}
|
||||
|
||||
class _MessageScreenState extends State<MessageScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
ListView(
|
||||
padding: EdgeInsets.all(16),
|
||||
children: [
|
||||
Center(
|
||||
child: Text('List of posts'),
|
||||
)
|
||||
],
|
||||
),
|
||||
MessageInput(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MessageInput extends StatefulWidget {
|
||||
const MessageInput({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<MessageInput> createState() => _MessageInputState();
|
||||
}
|
||||
|
||||
class _MessageInputState extends State<MessageInput> {
|
||||
final messageController = TextEditingController();
|
||||
|
||||
void submitData() {
|
||||
final newMessage = messageController;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(bottom: 16, left: 8, right: 8, top: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {},
|
||||
child: const Icon(
|
||||
Icons.add_a_photo,
|
||||
color: AppColors.mainDarkBlue,
|
||||
size: 30,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
style: const TextStyle(fontSize: 20),
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(20))),
|
||||
hintText: 'What\'s new?',
|
||||
hintStyle: const TextStyle(fontSize: 20),
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(
|
||||
Icons.send,
|
||||
color: AppColors.mainDarkBlue,
|
||||
size: 30,
|
||||
),
|
||||
onPressed: () {
|
||||
submitData();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NewMessage extends StatefulWidget {
|
||||
const NewMessage({super.key});
|
||||
|
||||
@override
|
||||
State<NewMessage> createState() => _NewMessageState();
|
||||
}
|
||||
|
||||
class _NewMessageState extends State<NewMessage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Placeholder();
|
||||
}
|
||||
}
|
@ -1,271 +0,0 @@
|
||||
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<ProfileScreen> createState() => ProfileScreenState();
|
||||
}
|
||||
|
||||
class ProfileScreenState extends State<ProfileScreen> {
|
||||
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<bool> 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<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');
|
||||
// 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<bool> _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<void> _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);
|
||||
},
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
@ -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',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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',
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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',
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
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 StatelessWidget {
|
||||
const UserNameWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Align(
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
'Username',
|
||||
style: TextStyle(fontSize: 25),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user