rearranged files, icon placeholder, drifter colors added #1
@ -1,7 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:drifter/pages/main_screen/main_screen_widget.dart';
|
||||||
import 'package:drifter/theme/app_colors.dart';
|
import 'package:drifter/theme/app_colors.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:drifter/widgets/main_screen/main_screen_widget.dart';
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
@ -15,11 +14,10 @@ class MyApp extends StatelessWidget {
|
|||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'Flutter Demo',
|
title: 'Flutter Demo',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
appBarTheme: const AppBarTheme(backgroundColor: AppColors.mainDarkBlue),
|
appBarTheme: const AppBarTheme(backgroundColor: AppColors.background),
|
||||||
primarySwatch: Colors.blue,
|
|
||||||
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
|
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
|
||||||
backgroundColor: AppColors.mainDarkBlue,
|
backgroundColor: AppColors.background,
|
||||||
selectedItemColor: Colors.white,
|
selectedItemColor: AppColors.mainAccent,
|
||||||
unselectedItemColor: Colors.grey,
|
unselectedItemColor: Colors.grey,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
457
lib/pages/home_screen/home_screen_widget.dart
Normal file
457
lib/pages/home_screen/home_screen_widget.dart
Normal file
@ -0,0 +1,457 @@
|
|||||||
|
// import 'dart:async';
|
||||||
|
// import 'dart:convert';
|
||||||
|
// import 'dart:html';
|
||||||
|
//
|
||||||
|
// 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/pages/home_screen/widgets/message_ok_button_widget.dart';
|
||||||
|
// import 'package:drifter/pages/home_screen/widgets/message_text_button_widget.dart';
|
||||||
|
// import 'package:drifter/pages/home_screen/widgets/message_text_form_field_widget.dart';
|
||||||
|
// import 'package:drifter/pages/profile_screen/profile_screen_widgets/message_snack_bar.dart';
|
||||||
|
//
|
||||||
|
// import 'package:drifter/pages/profile_screen/profile_screen.dart';
|
||||||
|
// import 'package:dart_nostr/dart_nostr.dart';
|
||||||
|
//
|
||||||
|
// class HomeScreen extends StatefulWidget {
|
||||||
|
// const HomeScreen({super.key});
|
||||||
|
//
|
||||||
|
// @override
|
||||||
|
// State<HomeScreen> createState() => _HomeScreenState();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// class _HomeScreenState extends State<HomeScreen> {
|
||||||
|
// bool _isConnected = false;
|
||||||
|
//
|
||||||
|
// final relay = Nostr.instance.relaysService.init(
|
||||||
|
// relaysUrl: ['wss://relay.damus.io'],
|
||||||
|
// onRelayListening: (String relayUrl, receivedData) {},
|
||||||
|
// onRelayError: (String relayUrl, Object? error) {},
|
||||||
|
// onRelayDone: (String relayUrl) {},
|
||||||
|
// lazyListeningToRelays: 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<StreamSubscription<NostrEvent>> _connectToRelay() async {
|
||||||
|
// // final stream = await relay.connect();
|
||||||
|
//
|
||||||
|
// // relay.on((event) {
|
||||||
|
// // if (event == RelayEvent.connect) {
|
||||||
|
// // setState(() => _isConnected = true);
|
||||||
|
// // } else if (event == RelayEvent.error) {
|
||||||
|
// // setState(() => _isConnected = false);
|
||||||
|
// // }
|
||||||
|
// // });
|
||||||
|
//
|
||||||
|
// NostrRequest req = NostrRequest(
|
||||||
|
// filters: [
|
||||||
|
// NostrFilter(
|
||||||
|
// kinds: [1],
|
||||||
|
// t: ["p", "..."],
|
||||||
|
// authors: ["..."],
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// final stream =
|
||||||
|
// Nostr.instance.relaysService.startEventsSubscription(request: req);
|
||||||
|
//
|
||||||
|
// // listening to the stream.
|
||||||
|
// stream.listen((event) {
|
||||||
|
// print(event);
|
||||||
|
// });
|
||||||
|
// return stream.listen((event) {
|
||||||
|
// print(event);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// void _initStream() async {
|
||||||
|
// _stream = (await _connectToRelay()) as Stream<Event>;
|
||||||
|
// _stream.listen((message) {
|
||||||
|
// final event = message;
|
||||||
|
// if (event.kind == 1) {
|
||||||
|
// setState(() => _events.add(event));
|
||||||
|
// relay.sub([
|
||||||
|
// NostrFilter(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 pages.
|
||||||
|
// 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 = NostrEvent(
|
||||||
|
// pubkey: '<THE-PUBKEY-OF-THE-EVENT-CREATOR>',
|
||||||
|
// kind: 0,
|
||||||
|
// content: 'This is a test event content',
|
||||||
|
// createdAt: DateTime.now(),
|
||||||
|
// id: '<THE-ID-OF-THE-EVENT>', // you will need to generate and set the id of the event manually by hashing other event fields, please refer to the official Nostr protocol documentation to learn how to do it yourself.
|
||||||
|
// tags: [],
|
||||||
|
// sig:
|
||||||
|
// '<THE-SIGNATURE-OF-THE-EVENT>', // you will need to generate and set the signature of the event manually by signing the event's id, please refer to the official Nostr protocol documentation to learn how to do it yourself.
|
||||||
|
// );
|
||||||
|
// if (eventApi.verifySignature(event)) {
|
||||||
|
// try {
|
||||||
|
// // If the signature is verified, the _relay method is called for the object to publish the event.
|
||||||
|
// 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,7 +1,9 @@
|
|||||||
|
// import 'package:drifter/pages/home_screen/home_screen_widget.dart';
|
||||||
|
import 'package:drifter/pages/message_screen/message_screen_widget.dart';
|
||||||
|
import 'package:drifter/pages/profile_screen/profile_screen.dart';
|
||||||
|
import 'package:drifter/theme/app_colors.dart';
|
||||||
|
import 'package:drifter/utilities/assets.dart';
|
||||||
import 'package:flutter/material.dart';
|
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';
|
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
|
||||||
class MainScreenWidget extends StatefulWidget {
|
class MainScreenWidget extends StatefulWidget {
|
||||||
@ -26,21 +28,31 @@ class _MainScreenWidgetState extends State<MainScreenWidget> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Row(
|
title: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
// mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
SvgPicture.asset(
|
SvgPicture.asset(
|
||||||
'assets/images/logo/drifter_logo_circle.svg',
|
Assets.svg.drifterIcon,
|
||||||
height: 40,
|
height: 30,
|
||||||
|
width: 30,
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 125,
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
"Drifter",
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppColors.mainAccent,
|
||||||
|
),
|
||||||
|
// textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
SizedBox(width: 10),
|
|
||||||
Text('Drifter')
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
),
|
),
|
||||||
body: IndexedStack(
|
body: IndexedStack(
|
||||||
index: _selectedTap,
|
index: _selectedTap,
|
||||||
children: [
|
children: const [
|
||||||
// HomeScreen(),
|
// HomeScreen(),
|
||||||
MessageScreen(),
|
MessageScreen(),
|
||||||
ProfileScreen(),
|
ProfileScreen(),
|
||||||
@ -48,7 +60,7 @@ class _MainScreenWidgetState extends State<MainScreenWidget> {
|
|||||||
),
|
),
|
||||||
bottomNavigationBar: BottomNavigationBar(
|
bottomNavigationBar: BottomNavigationBar(
|
||||||
currentIndex: _selectedTap,
|
currentIndex: _selectedTap,
|
||||||
items: [
|
items: const [
|
||||||
// BottomNavigationBarItem(
|
// BottomNavigationBarItem(
|
||||||
// icon: Icon(Icons.home),
|
// icon: Icon(Icons.home),
|
||||||
// label: 'Home',
|
// label: 'Home',
|
30
lib/pages/message_screen/message_screen_widget.dart
Normal file
30
lib/pages/message_screen/message_screen_widget.dart
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import 'package:drifter/pages/message_screen/widgets/message_input.dart';
|
||||||
|
import 'package:flutter/material.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: const EdgeInsets.all(16),
|
||||||
|
children: const [
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
"List of posts",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const MessageInput(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
96
lib/pages/message_screen/widgets/message_input.dart
Normal file
96
lib/pages/message_screen/widgets/message_input.dart
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import 'package:drifter/theme/app_colors.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class MessageInput extends StatefulWidget {
|
||||||
|
const MessageInput({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MessageInput> createState() => _MessageInputState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MessageInputState extends State<MessageInput> {
|
||||||
|
late final TextEditingController messageController;
|
||||||
|
late final FocusNode messageFocusNode;
|
||||||
|
|
||||||
|
void submitData() {
|
||||||
|
final newMessage = messageController;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
messageController = TextEditingController();
|
||||||
|
messageFocusNode = FocusNode();
|
||||||
|
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
messageController.dispose();
|
||||||
|
messageFocusNode.dispose();
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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: Container(
|
||||||
|
child: Icon(
|
||||||
|
Icons.add_a_photo,
|
||||||
|
color: AppColors.mainAccent,
|
||||||
|
size: 40,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 8,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: 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: 'What\'s new?',
|
||||||
|
hintStyle: const TextStyle(fontSize: 14),
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.send,
|
||||||
|
color: AppColors.mainDarkBlue,
|
||||||
|
size: 30,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
submitData();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
15
lib/pages/message_screen/widgets/new_message.dart
Normal file
15
lib/pages/message_screen/widgets/new_message.dart
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
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,15 +1,13 @@
|
|||||||
|
import 'package:dart_nostr/dart_nostr.dart';
|
||||||
|
import 'package:drifter/models/keys.dart';
|
||||||
|
import 'package:drifter/pages/profile_screen/profile_screen_widgets/delete_keys_dialog.dart';
|
||||||
|
import 'package:drifter/pages/profile_screen/profile_screen_widgets/key_exist_dialog.dart';
|
||||||
|
import 'package:drifter/pages/profile_screen/profile_screen_widgets/keys_option_modal_bottom_sheet.dart';
|
||||||
|
import 'package:drifter/pages/profile_screen/profile_screen_widgets/message_snack_bar.dart';
|
||||||
|
import 'package:drifter/pages/profile_screen/profile_screen_widgets/user_info_widget.dart';
|
||||||
|
import 'package:drifter/theme/app_colors.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.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:dart_nostr/dart_nostr.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 {
|
class ProfileScreen extends StatefulWidget {
|
||||||
const ProfileScreen({super.key});
|
const ProfileScreen({super.key});
|
||||||
@ -36,6 +34,7 @@ class ProfileScreenState extends State<ProfileScreen> {
|
|||||||
Future<bool> generateNewKeys() async {
|
Future<bool> generateNewKeys() async {
|
||||||
final newPrivateKey = await Nostr.instance.keysService.generatePrivateKey();
|
final newPrivateKey = await Nostr.instance.keysService.generatePrivateKey();
|
||||||
// final newPrivateKey = keyGenerator.generatePrivateKey();
|
// final newPrivateKey = keyGenerator.generatePrivateKey();
|
||||||
|
|
||||||
final nsec =
|
final nsec =
|
||||||
Nostr.instance.keysService.encodePrivateKeyToNsec(newPrivateKey);
|
Nostr.instance.keysService.encodePrivateKeyToNsec(newPrivateKey);
|
||||||
final nsecDecoded =
|
final nsecDecoded =
|
||||||
@ -46,11 +45,13 @@ class ProfileScreenState extends State<ProfileScreen> {
|
|||||||
final newPublicKey = await Nostr.instance.keysService
|
final newPublicKey = await Nostr.instance.keysService
|
||||||
.derivePublicKey(privateKey: newPrivateKey);
|
.derivePublicKey(privateKey: newPrivateKey);
|
||||||
// final newPublicKey = keyGenerator.getPublicKey(newPrivateKey);
|
// final newPublicKey = keyGenerator.getPublicKey(newPrivateKey);
|
||||||
|
|
||||||
final npub = Nostr.instance.keysService.encodePublicKeyToNpub(newPublicKey);
|
final npub = Nostr.instance.keysService.encodePublicKeyToNpub(newPublicKey);
|
||||||
final npubDecoded =
|
final npubDecoded =
|
||||||
Nostr.instance.keysService.decodeNpubKeyToPublicKey(npub);
|
Nostr.instance.keysService.decodeNpubKeyToPublicKey(npub);
|
||||||
// assert(npubDecoded['type'] == 'npub');
|
// assert(npubDecoded['type'] == 'npub');
|
||||||
// assert(npubDecoded['data'] == newPublicKey);
|
// assert(npubDecoded['data'] == newPublicKey);
|
||||||
|
|
||||||
return await _addKeyToStorage(newPrivateKey, newPublicKey, nsec, npub);
|
return await _addKeyToStorage(newPrivateKey, newPublicKey, nsec, npub);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,8 +59,10 @@ class ProfileScreenState extends State<ProfileScreen> {
|
|||||||
// Reading values associated with the " privateKey " and " publicKey " keys from a secure repository
|
// Reading values associated with the " privateKey " and " publicKey " keys from a secure repository
|
||||||
final storedPrivateKey = await _secureStorage.read(key: 'privateKey');
|
final storedPrivateKey = await _secureStorage.read(key: 'privateKey');
|
||||||
final storedPublicKey = await _secureStorage.read(key: 'publicKey');
|
final storedPublicKey = await _secureStorage.read(key: 'publicKey');
|
||||||
|
|
||||||
final storedNsecKey = await _secureStorage.read(key: 'nsec');
|
final storedNsecKey = await _secureStorage.read(key: 'nsec');
|
||||||
final storedNpubKey = await _secureStorage.read(key: 'npub');
|
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
|
// Indicates that both private and public keys are stored in a secure repository, after which, the state variables are updated
|
||||||
if (storedPrivateKey != null &&
|
if (storedPrivateKey != null &&
|
||||||
storedPublicKey != null &&
|
storedPublicKey != null &&
|
@ -0,0 +1,97 @@
|
|||||||
|
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: () {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
abstract class AppColors {
|
abstract class AppColors {
|
||||||
|
static const background = const Color(0xFF4f46f1);
|
||||||
|
static const mainAccent = const Color(0xFFFFCC11);
|
||||||
|
|
||||||
static const mainDarkBlue = Color.fromRGBO(3, 37, 65, 1);
|
static const mainDarkBlue = Color.fromRGBO(3, 37, 65, 1);
|
||||||
static const mainLightBlue = Color.fromRGBO(48, 86, 117, 1);
|
static const mainLightBlue = Color.fromRGBO(48, 86, 117, 1);
|
||||||
}
|
}
|
||||||
|
16
lib/utilities/assets.dart
Normal file
16
lib/utilities/assets.dart
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
abstract class Assets {
|
||||||
|
static const svg = _SVG();
|
||||||
|
static const png = _PNG();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PNG {
|
||||||
|
const _PNG();
|
||||||
|
|
||||||
|
// String get drifterIcon => "assets/images/logo/drifter_vector.png";
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SVG {
|
||||||
|
const _SVG();
|
||||||
|
|
||||||
|
String get drifterIcon => "assets/images/logo/drifter_logo_circle.svg";
|
||||||
|
}
|
@ -1,457 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:html';
|
|
||||||
|
|
||||||
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:drifter/widgets/profile_screen/profile_screen.dart';
|
|
||||||
import 'package:dart_nostr/dart_nostr.dart';
|
|
||||||
|
|
||||||
class HomeScreen extends StatefulWidget {
|
|
||||||
const HomeScreen({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<HomeScreen> createState() => _HomeScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _HomeScreenState extends State<HomeScreen> {
|
|
||||||
bool _isConnected = false;
|
|
||||||
|
|
||||||
final relay = Nostr.instance.relaysService.init(
|
|
||||||
relaysUrl: ['wss://relay.damus.io'],
|
|
||||||
onRelayListening: (String relayUrl, receivedData) {},
|
|
||||||
onRelayError: (String relayUrl, Object? error) {},
|
|
||||||
onRelayDone: (String relayUrl) {},
|
|
||||||
lazyListeningToRelays: 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<StreamSubscription<NostrEvent>> _connectToRelay() async {
|
|
||||||
// final stream = await relay.connect();
|
|
||||||
|
|
||||||
// relay.on((event) {
|
|
||||||
// if (event == RelayEvent.connect) {
|
|
||||||
// setState(() => _isConnected = true);
|
|
||||||
// } else if (event == RelayEvent.error) {
|
|
||||||
// setState(() => _isConnected = false);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
NostrRequest req = NostrRequest(
|
|
||||||
filters: [
|
|
||||||
NostrFilter(
|
|
||||||
kinds: [1],
|
|
||||||
t: ["p", "..."],
|
|
||||||
authors: ["..."],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
final stream =
|
|
||||||
Nostr.instance.relaysService.startEventsSubscription(request: req);
|
|
||||||
|
|
||||||
// listening to the stream.
|
|
||||||
stream.listen((event) {
|
|
||||||
print(event);
|
|
||||||
});
|
|
||||||
return stream.listen((event) {
|
|
||||||
print(event);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _initStream() async {
|
|
||||||
_stream = (await _connectToRelay()) as Stream<Event>;
|
|
||||||
_stream.listen((message) {
|
|
||||||
final event = message;
|
|
||||||
if (event.kind == 1) {
|
|
||||||
setState(() => _events.add(event));
|
|
||||||
relay.sub([
|
|
||||||
NostrFilter(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 = NostrEvent(
|
|
||||||
pubkey: '<THE-PUBKEY-OF-THE-EVENT-CREATOR>',
|
|
||||||
kind: 0,
|
|
||||||
content: 'This is a test event content',
|
|
||||||
createdAt: DateTime.now(),
|
|
||||||
id: '<THE-ID-OF-THE-EVENT>', // you will need to generate and set the id of the event manually by hashing other event fields, please refer to the official Nostr protocol documentation to learn how to do it yourself.
|
|
||||||
tags: [],
|
|
||||||
sig:
|
|
||||||
'<THE-SIGNATURE-OF-THE-EVENT>', // you will need to generate and set the signature of the event manually by signing the event's id, please refer to the official Nostr protocol documentation to learn how to do it yourself.
|
|
||||||
);
|
|
||||||
if (eventApi.verifySignature(event)) {
|
|
||||||
try {
|
|
||||||
// If the signature is verified, the _relay method is called for the object to publish the event.
|
|
||||||
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,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,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),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -63,7 +63,8 @@ flutter:
|
|||||||
|
|
||||||
# To add assets to your application, add an assets section, like this:
|
# To add assets to your application, add an assets section, like this:
|
||||||
assets:
|
assets:
|
||||||
- assets/images/logo/
|
- assets/images/logo/drifter_vector.png
|
||||||
|
- assets/images/logo/drifter_logo_circle.svg
|
||||||
# - images/a_dot_ham.jpeg
|
# - images/a_dot_ham.jpeg
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
|
@ -2,14 +2,13 @@
|
|||||||
//
|
//
|
||||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||||
// utility in the flutter_test package. For example, you can send tap and scroll
|
// utility in the flutter_test package. For example, you can send tap and scroll
|
||||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
// gestures. You can also use WidgetTester to find child pages in the widget
|
||||||
// tree, read text, and verify that the values of widget properties are correct.
|
// tree, read text, and verify that the values of widget properties are correct.
|
||||||
|
|
||||||
|
import 'package:drifter/main.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
import 'package:drifter/main.dart';
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||||
// Build our app and trigger a frame.
|
// Build our app and trigger a frame.
|
||||||
|
Loading…
Reference in New Issue
Block a user