forked from alexvasl/drifter_app
Added Splash screen, application works with nostr_tools.
This commit is contained in:
parent
17cd8b0f6e
commit
000a7bd433
@ -1,5 +1,9 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:drifter/pages/main_screen/main_screen_widget.dart';
|
import 'package:drifter/pages/main_screen/main_screen_widget.dart';
|
||||||
|
import 'package:drifter/pages/splash_screen/splash_screen.dart';
|
||||||
import 'package:drifter/theme/app_colors.dart';
|
import 'package:drifter/theme/app_colors.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
@ -21,7 +25,7 @@ class MyApp extends StatelessWidget {
|
|||||||
unselectedItemColor: Colors.grey,
|
unselectedItemColor: Colors.grey,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
home: const MainScreenWidget(),
|
home: const Splash(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import 'package:dart_nostr/dart_nostr.dart';
|
import 'package:nostr_tools/nostr_tools.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class Keys {
|
class Keys {
|
||||||
static String privateKey = '';
|
static String privateKey = '';
|
||||||
@ -8,3 +7,7 @@ class Keys {
|
|||||||
static String npubKey = '';
|
static String npubKey = '';
|
||||||
static bool keysExist = false;
|
static bool keysExist = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Relay {
|
||||||
|
static final relay = RelayApi(relayUrl: 'wss://relay.damus.io');
|
||||||
|
}
|
||||||
|
@ -1,457 +1,440 @@
|
|||||||
// import 'dart:async';
|
import 'dart:async';
|
||||||
// import 'dart:convert';
|
import 'dart:convert';
|
||||||
// import 'dart:html';
|
|
||||||
//
|
import 'package:flutter/material.dart';
|
||||||
// import 'package:flutter/material.dart';
|
import 'package:drifter/domain_models/domain_models.dart';
|
||||||
// import 'package:drifter/domain_models/domain_models.dart';
|
import 'package:drifter/models/keys.dart';
|
||||||
// import 'package:drifter/models/keys.dart';
|
import 'package:drifter/theme/app_colors.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_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_button_widget.dart';
|
import 'package:drifter/pages/home_screen/widgets/message_text_form_field_widget.dart';
|
||||||
// import 'package:drifter/pages/home_screen/widgets/message_text_form_field_widget.dart';
|
import 'package:drifter/pages/profile_screen/widgets/message_snack_bar.dart';
|
||||||
// import 'package:drifter/pages/profile_screen/profile_screen_widgets/message_snack_bar.dart';
|
import 'package:nostr_tools/nostr_tools.dart';
|
||||||
//
|
|
||||||
// import 'package:drifter/pages/profile_screen/profile_screen.dart';
|
class HomeScreen extends StatefulWidget {
|
||||||
// import 'package:dart_nostr/dart_nostr.dart';
|
const HomeScreen({super.key});
|
||||||
//
|
|
||||||
// class HomeScreen extends StatefulWidget {
|
@override
|
||||||
// const HomeScreen({super.key});
|
State<HomeScreen> createState() => _HomeScreenState();
|
||||||
//
|
}
|
||||||
// @override
|
|
||||||
// State<HomeScreen> createState() => _HomeScreenState();
|
class _HomeScreenState extends State<HomeScreen> {
|
||||||
// }
|
bool _isConnected = false;
|
||||||
//
|
|
||||||
// class _HomeScreenState extends State<HomeScreen> {
|
final List<Event> _events = [];
|
||||||
// bool _isConnected = false;
|
final Map<String, Metadata> _metaDatas = {};
|
||||||
//
|
late Stream<Event> _stream;
|
||||||
// final relay = Nostr.instance.relaysService.init(
|
final _controller = StreamController<Event>();
|
||||||
// relaysUrl: ['wss://relay.damus.io'],
|
|
||||||
// onRelayListening: (String relayUrl, receivedData) {},
|
bool _isNotePublishing = false;
|
||||||
// onRelayError: (String relayUrl, Object? error) {},
|
|
||||||
// onRelayDone: (String relayUrl) {},
|
@override
|
||||||
// lazyListeningToRelays: false,
|
void initState() {
|
||||||
// );
|
_initStream();
|
||||||
// final List<Event> _events = [];
|
super.initState();
|
||||||
// final Map<String, Metadata> _metaDatas = {};
|
}
|
||||||
// late Stream<Event> _stream;
|
|
||||||
// final _controller = StreamController<Event>();
|
Future<Stream<Event>> _connectToRelay() async {
|
||||||
//
|
final stream = await Relay.relay.connect();
|
||||||
// bool _isNotePublishing = false;
|
|
||||||
//
|
Relay.relay.on((event) {
|
||||||
// @override
|
if (event == RelayEvent.connect) {
|
||||||
// void initState() {
|
setState(() => _isConnected = true);
|
||||||
// _initStream();
|
} else if (event == RelayEvent.error) {
|
||||||
// super.initState();
|
setState(() => _isConnected = false);
|
||||||
// }
|
}
|
||||||
//
|
});
|
||||||
// Future<StreamSubscription<NostrEvent>> _connectToRelay() async {
|
|
||||||
// // final stream = await relay.connect();
|
Relay.relay.sub([
|
||||||
//
|
Filter(
|
||||||
// // relay.on((event) {
|
kinds: [1],
|
||||||
// // if (event == RelayEvent.connect) {
|
limit: 100,
|
||||||
// // setState(() => _isConnected = true);
|
t: ['nostr'],
|
||||||
// // } else if (event == RelayEvent.error) {
|
)
|
||||||
// // setState(() => _isConnected = false);
|
]);
|
||||||
// // }
|
|
||||||
// // });
|
return stream
|
||||||
//
|
.where((message) => message.type == 'EVENT')
|
||||||
// NostrRequest req = NostrRequest(
|
.map((message) => message.message);
|
||||||
// filters: [
|
}
|
||||||
// NostrFilter(
|
|
||||||
// kinds: [1],
|
void _initStream() async {
|
||||||
// t: ["p", "..."],
|
_stream = await _connectToRelay();
|
||||||
// authors: ["..."],
|
_stream.listen((message) {
|
||||||
// ),
|
final event = message;
|
||||||
// ],
|
if (event.kind == 1) {
|
||||||
// );
|
setState(() => _events.add(event));
|
||||||
//
|
Relay.relay.sub([
|
||||||
// final stream =
|
Filter(kinds: [0], authors: [event.pubkey])
|
||||||
// Nostr.instance.relaysService.startEventsSubscription(request: req);
|
]);
|
||||||
//
|
} else if (event.kind == 0) {
|
||||||
// // listening to the stream.
|
final metadata = Metadata.fromJson(jsonDecode(event.content));
|
||||||
// stream.listen((event) {
|
setState(() => _metaDatas[event.pubkey] = metadata);
|
||||||
// print(event);
|
}
|
||||||
// });
|
_controller.add(event);
|
||||||
// return stream.listen((event) {
|
});
|
||||||
// print(event);
|
}
|
||||||
// });
|
|
||||||
// }
|
// The _resubscribeStream() method clears the _events and _metaData scollection after a 1 second delay
|
||||||
//
|
Future<void> _resubscribeStream() async {
|
||||||
// void _initStream() async {
|
await Future.delayed(const Duration(seconds: 1), () {
|
||||||
// _stream = (await _connectToRelay()) as Stream<Event>;
|
setState(() {
|
||||||
// _stream.listen((message) {
|
_events.clear();
|
||||||
// final event = message;
|
_metaDatas.clear();
|
||||||
// if (event.kind == 1) {
|
});
|
||||||
// setState(() => _events.add(event));
|
// _initStream() method, responsible for initializing and subscribing to the stream, to reconnect and re-subscribe to the filter.
|
||||||
// relay.sub([
|
_initStream();
|
||||||
// NostrFilter(kinds: [0], authors: [event.pubkey])
|
});
|
||||||
// ]);
|
}
|
||||||
// } else if (event.kind == 0) {
|
|
||||||
// final metadata = Metadata.fromJson(jsonDecode(event.content));
|
@override
|
||||||
// setState(() => _metaDatas[event.pubkey] = metadata);
|
Widget build(BuildContext context) {
|
||||||
// }
|
return Scaffold(
|
||||||
// _controller.add(event);
|
body: RefreshIndicator(
|
||||||
// });
|
onRefresh: () async {
|
||||||
// }
|
await _resubscribeStream();
|
||||||
//
|
},
|
||||||
// // The _resubscribeStream() method clears the _events and _metaData scollection after a 1 second delay
|
child: StreamBuilder(
|
||||||
// Future<void> _resubscribeStream() async {
|
stream: _controller.stream,
|
||||||
// await Future.delayed(const Duration(seconds: 1), () {
|
builder: (context, snapshot) {
|
||||||
// setState(() {
|
// Inside the builder callback, the snapshot object contains the most recent event from the thread.
|
||||||
// _events.clear();
|
// If snapshot.hasData is true, there is data to display. In this case, ListView.builder is returned, which displays a list of NoostCard widgets.
|
||||||
// _metaDatas.clear();
|
if (snapshot.hasData) {
|
||||||
// });
|
return ListView.builder(
|
||||||
// // _initStream() method, responsible for initializing and subscribing to the stream, to reconnect and re-subscribe to the filter.
|
// The itemCount property of ListView.builder is set to _events.length, , which is the number of events in the _events list.
|
||||||
// _initStream();
|
itemCount: _events.length,
|
||||||
// });
|
itemBuilder: (context, index) {
|
||||||
// }
|
final event = _events[index];
|
||||||
//
|
final metadata = _metaDatas[event.pubkey];
|
||||||
// @override
|
// For each event, a Noost object is created that encapsulates the details of the event, including id, avatarUrl, name,username, time, content и pubkey.
|
||||||
// Widget build(BuildContext context) {
|
// _metaDatas, you can map the event public key to the author's metadata.
|
||||||
// return Scaffold(
|
final domain = Domain(
|
||||||
// body: RefreshIndicator(
|
noteId: event.id,
|
||||||
// onRefresh: () async {
|
avatarUrl: metadata?.picture ??
|
||||||
// await _resubscribeStream();
|
'https://robohash.org/${event.pubkey}',
|
||||||
// },
|
name: metadata?.name ?? 'Anon',
|
||||||
// child: StreamBuilder(
|
username: metadata?.displayName ??
|
||||||
// stream: _controller.stream,
|
(metadata?.display_name ?? 'Anon'),
|
||||||
// builder: (context, snapshot) {
|
time: TimeAgo.format(event.created_at),
|
||||||
// // Inside the builder callback, the snapshot object contains the most recent event from the thread.
|
content: event.content,
|
||||||
// // If snapshot.hasData is true, there is data to display. In this case, ListView.builder is returned, which displays a list of NoostCard pages.
|
pubkey: event.pubkey,
|
||||||
// if (snapshot.hasData) {
|
);
|
||||||
// return ListView.builder(
|
return DomainCard(domain: domain);
|
||||||
// // 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) {
|
} else if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
// final event = _events[index];
|
return const Center(child: Text('Loading....'));
|
||||||
// final metadata = _metaDatas[event.pubkey];
|
} else if (snapshot.hasError) {
|
||||||
// // For each event, a Noost object is created that encapsulates the details of the event, including id, avatarUrl, name,username, time, content и pubkey.
|
return Center(child: Text('Error: ${snapshot.error}'));
|
||||||
// // _metaDatas, you can map the event public key to the author's metadata.
|
}
|
||||||
// final domain = Domain(
|
return const CenteredCircularProgressIndicator();
|
||||||
// noteId: event.id,
|
},
|
||||||
// avatarUrl: metadata?.picture ??
|
),
|
||||||
// 'https://robohash.org/${event.pubkey}',
|
),
|
||||||
// name: metadata?.name ?? 'Anon',
|
floatingActionButton: Keys.keysExist
|
||||||
// username: metadata?.displayName ??
|
? CreatePost(
|
||||||
// (metadata?.display_name ?? 'Anon'),
|
// The publishNote function is called when the user launches the "Noost!" button in the dialog box.
|
||||||
// time: TimeAgo.format(event.created_at),
|
publishNote: (note) {
|
||||||
// content: event.content,
|
setState(() => _isNotePublishing = true);
|
||||||
// pubkey: event.pubkey,
|
|
||||||
// );
|
// EventApi Creates an instance of the class defined in the nostr_tools package.
|
||||||
// return DomainCard(domain: domain);
|
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.
|
||||||
// } else if (snapshot.connectionState == ConnectionState.waiting) {
|
final event = eventApi.finishEvent(
|
||||||
// return const Center(child: Text('Loading....'));
|
// This creates a new instance of the Event class with certain properties, such as:
|
||||||
// } else if (snapshot.hasError) {
|
Event(
|
||||||
// return Center(child: Text('Error: ${snapshot.error}'));
|
kind: 1,
|
||||||
// }
|
tags: [
|
||||||
// return const CenteredCircularProgressIndicator();
|
['t', 'nostr']
|
||||||
// },
|
],
|
||||||
// ),
|
content: note!,
|
||||||
// ),
|
created_at: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||||
// floatingActionButton: Keys.keysExist
|
),
|
||||||
// ? CreatePost(
|
Keys.publicKey,
|
||||||
// // The publishNote function is called when the user launches the "Noost!" button in the dialog box.
|
);
|
||||||
// publishNote: (note) {
|
if (eventApi.verifySignature(event)) {
|
||||||
// setState(() => _isNotePublishing = true);
|
try {
|
||||||
//
|
// If the signature is verified, the _relay method is called for the object to publish the event.
|
||||||
// // EventApi Creates an instance of the class defined in the nostr_tools package.
|
Relay.relay.publish(event);
|
||||||
// final eventApi = EventApi();
|
// After the _resubscribeStream event is published, a method is called that will probably update the stream or subscription to reflect the recently published event.
|
||||||
// // The finishEvent method of the EventApi class is called with the Event object and the _privateKey variable.
|
_resubscribeStream();
|
||||||
// // finishEvent will set the event id with the event hash and sign the event with the given _privateKey.
|
// Show SnackBar to display a message that the note has been successfully published.
|
||||||
// final event = NostrEvent(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
// pubkey: '<THE-PUBKEY-OF-THE-EVENT-CREATOR>',
|
MessageSnackBar(
|
||||||
// kind: 0,
|
label: 'Congratulations! Noost Published!'),
|
||||||
// content: 'This is a test event content',
|
);
|
||||||
// createdAt: DateTime.now(),
|
} catch (_) {
|
||||||
// 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.
|
// If an error occurs during the publishing process (e.g., an exception is caught), SnackBar displays a warning instead.
|
||||||
// tags: [],
|
ScaffoldMessenger.of(context).showSnackBar(MessageSnackBar(
|
||||||
// sig:
|
label: 'Oops! Something went wrong!',
|
||||||
// '<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.
|
isWarning: true,
|
||||||
// );
|
));
|
||||||
// if (eventApi.verifySignature(event)) {
|
}
|
||||||
// try {
|
}
|
||||||
// // If the signature is verified, the _relay method is called for the object to publish the event.
|
setState(() => _isNotePublishing = false);
|
||||||
// relay.publish(event);
|
Navigator.pop(context);
|
||||||
// // 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();
|
isNotePublishing: _isNotePublishing,
|
||||||
// // Show SnackBar to display a message that the note has been successfully published.
|
)
|
||||||
// ScaffoldMessenger.of(context).showSnackBar(
|
:
|
||||||
// MessageSnackBar(
|
// If _keysExist is false, then an empty widget is displayed, which means that the FAB will not be visible. Container()
|
||||||
// label: 'Congratulations! Noost Published!'),
|
Container(),
|
||||||
// );
|
);
|
||||||
// } 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!',
|
class TimeAgo {
|
||||||
// isWarning: true,
|
static String format(int timestamp) {
|
||||||
// ));
|
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
|
||||||
// }
|
Duration difference = DateTime.now().difference(dateTime);
|
||||||
// }
|
|
||||||
// setState(() => _isNotePublishing = false);
|
String timeAgo = '';
|
||||||
// Navigator.pop(context);
|
|
||||||
// },
|
if (difference.inDays > 0) {
|
||||||
// isNotePublishing: _isNotePublishing,
|
timeAgo =
|
||||||
// )
|
'${difference.inDays} ${difference.inDays == 1 ? 'day' : 'days'} ago';
|
||||||
// :
|
} else if (difference.inHours > 0) {
|
||||||
// // If _keysExist is false, then an empty widget is displayed, which means that the FAB will not be visible. Container()
|
timeAgo =
|
||||||
// Container(),
|
'${difference.inHours} ${difference.inHours == 1 ? 'hour' : 'hours'} ago';
|
||||||
// );
|
} else if (difference.inMinutes > 0) {
|
||||||
// }
|
timeAgo =
|
||||||
// }
|
'${difference.inMinutes} ${difference.inMinutes == 1 ? 'minute' : 'minutes'} ago';
|
||||||
//
|
} else {
|
||||||
// class TimeAgo {
|
timeAgo = 'just now';
|
||||||
// static String format(int timestamp) {
|
}
|
||||||
// DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
|
|
||||||
// Duration difference = DateTime.now().difference(dateTime);
|
return timeAgo;
|
||||||
//
|
}
|
||||||
// String timeAgo = '';
|
}
|
||||||
//
|
|
||||||
// if (difference.inDays > 0) {
|
class DomainCard extends StatelessWidget {
|
||||||
// timeAgo =
|
const DomainCard({
|
||||||
// '${difference.inDays} ${difference.inDays == 1 ? 'day' : 'days'} ago';
|
super.key,
|
||||||
// } else if (difference.inHours > 0) {
|
required this.domain,
|
||||||
// timeAgo =
|
});
|
||||||
// '${difference.inHours} ${difference.inHours == 1 ? 'hour' : 'hours'} ago';
|
|
||||||
// } else if (difference.inMinutes > 0) {
|
final Domain domain;
|
||||||
// timeAgo =
|
|
||||||
// '${difference.inMinutes} ${difference.inMinutes == 1 ? 'minute' : 'minutes'} ago';
|
List<String>? extractImage(String text) {
|
||||||
// } else {
|
final RegExp exp = RegExp(
|
||||||
// timeAgo = 'just now';
|
r"(http(s?):)([/|.|\w|\s|-])*\.(?:jpg|gif|png|jpeg)",
|
||||||
// }
|
caseSensitive: false,
|
||||||
//
|
multiLine: true,
|
||||||
// return timeAgo;
|
);
|
||||||
// }
|
|
||||||
// }
|
final Iterable<Match> matches = exp.allMatches(text);
|
||||||
//
|
|
||||||
// class DomainCard extends StatelessWidget {
|
final List<String> imageLinks =
|
||||||
// const DomainCard({
|
matches.map((match) => match.group(0)!).toList();
|
||||||
// super.key,
|
|
||||||
// required this.domain,
|
return imageLinks.isNotEmpty ? imageLinks : null;
|
||||||
// });
|
}
|
||||||
//
|
|
||||||
// final Domain domain;
|
@override
|
||||||
//
|
Widget build(BuildContext context) {
|
||||||
// List<String>? extractImage(String text) {
|
final List<String>? imageLinks = extractImage(domain.content);
|
||||||
// final RegExp exp = RegExp(
|
return Container(
|
||||||
// r"(http(s?):)([/|.|\w|\s|-])*\.(?:jpg|gif|png|jpeg)",
|
margin: const EdgeInsets.all(8),
|
||||||
// caseSensitive: false,
|
decoration: BoxDecoration(
|
||||||
// multiLine: true,
|
color: AppColors.mainLightBlue,
|
||||||
// );
|
borderRadius: BorderRadius.circular(10),
|
||||||
//
|
boxShadow: [
|
||||||
// final Iterable<Match> matches = exp.allMatches(text);
|
BoxShadow(
|
||||||
//
|
color: Colors.grey.withOpacity(0.5),
|
||||||
// final List<String> imageLinks =
|
spreadRadius: 2,
|
||||||
// matches.map((match) => match.group(0)!).toList();
|
blurRadius: 5,
|
||||||
//
|
offset: const Offset(0, 3),
|
||||||
// return imageLinks.isNotEmpty ? imageLinks : null;
|
),
|
||||||
// }
|
],
|
||||||
//
|
),
|
||||||
// @override
|
child: Column(
|
||||||
// Widget build(BuildContext context) {
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
// final List<String>? imageLinks = extractImage(domain.content);
|
children: [
|
||||||
// return Container(
|
ListTile(
|
||||||
// margin: const EdgeInsets.all(8),
|
leading: CircleAvatar(
|
||||||
// decoration: BoxDecoration(
|
backgroundImage: FadeInImage(
|
||||||
// color: AppColors.mainLightBlue,
|
placeholder:
|
||||||
// borderRadius: BorderRadius.circular(10),
|
const NetworkImage('https://i.ibb.co/mJkxDkb/satoshi.png'),
|
||||||
// boxShadow: [
|
image: NetworkImage(domain.avatarUrl),
|
||||||
// BoxShadow(
|
).image,
|
||||||
// color: Colors.grey.withOpacity(0.5),
|
),
|
||||||
// spreadRadius: 2,
|
title:
|
||||||
// blurRadius: 5,
|
Text(domain.name, style: const TextStyle(color: Colors.white)),
|
||||||
// offset: const Offset(0, 3),
|
subtitle: Text('@${domain.username.toLowerCase()} • ${domain.time}',
|
||||||
// ),
|
style: TextStyle(color: Colors.grey.shade400)),
|
||||||
// ],
|
trailing: const Icon(Icons.more_vert, color: Colors.grey),
|
||||||
// ),
|
),
|
||||||
// child: Column(
|
Divider(height: 1, color: Colors.grey.shade400),
|
||||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
Padding(
|
||||||
// children: [
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
// ListTile(
|
child: Text(domain.content,
|
||||||
// leading: CircleAvatar(
|
style: const TextStyle(color: Colors.white)),
|
||||||
// backgroundImage: FadeInImage(
|
),
|
||||||
// placeholder:
|
if (imageLinks != null && imageLinks.isNotEmpty)
|
||||||
// const NetworkImage('https://i.ibb.co/mJkxDkb/satoshi.png'),
|
Center(
|
||||||
// image: NetworkImage(domain.avatarUrl),
|
child: Stack(
|
||||||
// ).image,
|
children: [
|
||||||
// ),
|
const Placeholder(
|
||||||
// title:
|
fallbackHeight: 200,
|
||||||
// Text(domain.name, style: const TextStyle(color: Colors.white)),
|
color: Colors.transparent,
|
||||||
// subtitle: Text('@${domain.username.toLowerCase()} • ${domain.time}',
|
),
|
||||||
// style: TextStyle(color: Colors.grey.shade400)),
|
Center(
|
||||||
// trailing: const Icon(Icons.more_vert, color: Colors.grey),
|
child: FadeInImage(
|
||||||
// ),
|
placeholder: const NetworkImage(
|
||||||
// Divider(height: 1, color: Colors.grey.shade400),
|
'https://i.ibb.co/D9jqXgR/58038897-167f0280-7ae6-11e9-94eb-88e880a25f0f.gif',
|
||||||
// Padding(
|
),
|
||||||
// padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
image: NetworkImage(imageLinks.first),
|
||||||
// child: Text(domain.content,
|
fit: BoxFit.cover,
|
||||||
// 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(
|
class CenteredCircularProgressIndicator extends StatelessWidget {
|
||||||
// placeholder: const NetworkImage(
|
const CenteredCircularProgressIndicator({Key? key}) : super(key: key);
|
||||||
// 'https://i.ibb.co/D9jqXgR/58038897-167f0280-7ae6-11e9-94eb-88e880a25f0f.gif',
|
|
||||||
// ),
|
@override
|
||||||
// image: NetworkImage(imageLinks.first),
|
Widget build(BuildContext context) {
|
||||||
// fit: BoxFit.cover,
|
return const Center(
|
||||||
// ),
|
child: CircularProgressIndicator(),
|
||||||
// ),
|
);
|
||||||
// ],
|
}
|
||||||
// ),
|
}
|
||||||
// ),
|
|
||||||
// ],
|
class CreatePost extends StatefulWidget {
|
||||||
// ),
|
const CreatePost({
|
||||||
// );
|
Key? key,
|
||||||
// }
|
required this.publishNote,
|
||||||
// }
|
required this.isNotePublishing,
|
||||||
//
|
}) : super(key: key);
|
||||||
// class CenteredCircularProgressIndicator extends StatelessWidget {
|
|
||||||
// const CenteredCircularProgressIndicator({Key? key}) : super(key: key);
|
final Function(String?) publishNote;
|
||||||
//
|
final bool isNotePublishing;
|
||||||
// @override
|
|
||||||
// Widget build(BuildContext context) {
|
@override
|
||||||
// return const Center(
|
State<CreatePost> createState() => _CreatePostState();
|
||||||
// child: CircularProgressIndicator(),
|
}
|
||||||
// );
|
|
||||||
// }
|
class _CreatePostState extends State<CreatePost> {
|
||||||
// }
|
final _noteController = TextEditingController();
|
||||||
//
|
final GlobalKey<FormFieldState> _formKey = GlobalKey<FormFieldState>();
|
||||||
// class CreatePost extends StatefulWidget {
|
|
||||||
// const CreatePost({
|
@override
|
||||||
// Key? key,
|
Widget build(BuildContext context) {
|
||||||
// required this.publishNote,
|
return FloatingActionButton(
|
||||||
// required this.isNotePublishing,
|
backgroundColor: Colors.deepPurpleAccent,
|
||||||
// }) : super(key: key);
|
tooltip: 'Create a new post',
|
||||||
//
|
elevation: 2,
|
||||||
// final Function(String?) publishNote;
|
highlightElevation: 4,
|
||||||
// final bool isNotePublishing;
|
foregroundColor: Colors.white,
|
||||||
//
|
child: Stack(
|
||||||
// @override
|
alignment: Alignment.center,
|
||||||
// State<CreatePost> createState() => _CreatePostState();
|
children: [
|
||||||
// }
|
Container(
|
||||||
//
|
width: 60,
|
||||||
// class _CreatePostState extends State<CreatePost> {
|
height: 60,
|
||||||
// final _noteController = TextEditingController();
|
decoration: const BoxDecoration(
|
||||||
// final GlobalKey<FormFieldState> _formKey = GlobalKey<FormFieldState>();
|
shape: BoxShape.circle, color: AppColors.mainDarkBlue),
|
||||||
//
|
),
|
||||||
// @override
|
const Icon(
|
||||||
// Widget build(BuildContext context) {
|
Icons.add,
|
||||||
// return FloatingActionButton(
|
color: Colors.white,
|
||||||
// backgroundColor: Colors.deepPurpleAccent,
|
),
|
||||||
// tooltip: 'Create a new post',
|
],
|
||||||
// elevation: 2,
|
),
|
||||||
// highlightElevation: 4,
|
onPressed: () async {
|
||||||
// foregroundColor: Colors.white,
|
_noteController.clear();
|
||||||
// child: Stack(
|
await showDialog(
|
||||||
// alignment: Alignment.center,
|
barrierDismissible: false,
|
||||||
// children: [
|
context: context,
|
||||||
// Container(
|
builder: ((context) {
|
||||||
// width: 60,
|
return Dialog(
|
||||||
// height: 60,
|
shape: RoundedRectangleBorder(
|
||||||
// decoration: const BoxDecoration(
|
borderRadius: BorderRadius.circular(12),
|
||||||
// shape: BoxShape.circle, color: AppColors.mainDarkBlue),
|
),
|
||||||
// ),
|
child: Container(
|
||||||
// const Icon(
|
constraints: const BoxConstraints(maxWidth: 600),
|
||||||
// Icons.add,
|
decoration: BoxDecoration(
|
||||||
// color: Colors.white,
|
borderRadius: BorderRadius.circular(12),
|
||||||
// ),
|
color: Colors.white,
|
||||||
// ],
|
),
|
||||||
// ),
|
child: Column(
|
||||||
// onPressed: () async {
|
mainAxisSize: MainAxisSize.min,
|
||||||
// _noteController.clear();
|
children: [
|
||||||
// await showDialog(
|
Container(
|
||||||
// barrierDismissible: false,
|
padding: const EdgeInsets.symmetric(vertical: 24),
|
||||||
// context: context,
|
decoration: BoxDecoration(
|
||||||
// builder: ((context) {
|
borderRadius: BorderRadius.circular(12),
|
||||||
// return Dialog(
|
color: AppColors.mainDarkBlue,
|
||||||
// shape: RoundedRectangleBorder(
|
),
|
||||||
// borderRadius: BorderRadius.circular(12),
|
child: const Center(
|
||||||
// ),
|
child: Text(
|
||||||
// child: Container(
|
'Create a Noost',
|
||||||
// constraints: const BoxConstraints(maxWidth: 600),
|
style: TextStyle(
|
||||||
// decoration: BoxDecoration(
|
fontSize: 24,
|
||||||
// borderRadius: BorderRadius.circular(12),
|
fontWeight: FontWeight.bold,
|
||||||
// color: Colors.white,
|
color: Colors.white,
|
||||||
// ),
|
),
|
||||||
// child: Column(
|
),
|
||||||
// mainAxisSize: MainAxisSize.min,
|
),
|
||||||
// children: [
|
),
|
||||||
// Container(
|
const SizedBox(height: 24),
|
||||||
// padding: const EdgeInsets.symmetric(vertical: 24),
|
Padding(
|
||||||
// decoration: BoxDecoration(
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
// borderRadius: BorderRadius.circular(12),
|
child: MessageTextFormField(
|
||||||
// color: AppColors.mainDarkBlue,
|
maxLines: 5,
|
||||||
// ),
|
hintText: 'Type your Noost here...',
|
||||||
// child: const Center(
|
controller: _noteController,
|
||||||
// child: Text(
|
formKey: _formKey,
|
||||||
// 'Create a Noost',
|
validator: (value) {
|
||||||
// style: TextStyle(
|
if (value == null || value.trim().isEmpty) {
|
||||||
// fontSize: 24,
|
return 'Please enter your note.';
|
||||||
// fontWeight: FontWeight.bold,
|
}
|
||||||
// color: Colors.white,
|
return null;
|
||||||
// ),
|
},
|
||||||
// ),
|
),
|
||||||
// ),
|
),
|
||||||
// ),
|
const SizedBox(height: 24),
|
||||||
// const SizedBox(height: 24),
|
widget.isNotePublishing
|
||||||
// Padding(
|
? const CenteredCircularProgressIndicator()
|
||||||
// padding: const EdgeInsets.symmetric(horizontal: 24),
|
: Row(
|
||||||
// child: MessageTextFormField(
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
// maxLines: 5,
|
children: [
|
||||||
// hintText: 'Type your Noost here...',
|
MessageTextButton(
|
||||||
// controller: _noteController,
|
onPressed: () {
|
||||||
// formKey: _formKey,
|
Navigator.pop(context);
|
||||||
// validator: (value) {
|
},
|
||||||
// if (value == null || value.trim().isEmpty) {
|
label: 'Cancel',
|
||||||
// return 'Please enter your note.';
|
),
|
||||||
// }
|
const SizedBox(width: 16),
|
||||||
// return null;
|
MessageOkButton(
|
||||||
// },
|
onPressed: () {
|
||||||
// ),
|
if (_formKey.currentState!.validate()) {
|
||||||
// ),
|
widget.publishNote(
|
||||||
// const SizedBox(height: 24),
|
_noteController.text.trim());
|
||||||
// widget.isNotePublishing
|
} else {
|
||||||
// ? const CenteredCircularProgressIndicator()
|
_formKey.currentState?.setState(() {});
|
||||||
// : Row(
|
}
|
||||||
// mainAxisAlignment: MainAxisAlignment.end,
|
},
|
||||||
// children: [
|
label: 'Noost!',
|
||||||
// MessageTextButton(
|
),
|
||||||
// onPressed: () {
|
const SizedBox(width: 24),
|
||||||
// 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,4 +1,5 @@
|
|||||||
// import 'package:drifter/pages/home_screen/home_screen_widget.dart';
|
// import 'package:drifter/pages/home_screen/home_screen_widget.dart';
|
||||||
|
import 'package:drifter/pages/home_screen/home_screen_widget.dart';
|
||||||
import 'package:drifter/pages/message_screen/message_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/pages/profile_screen/profile_screen.dart';
|
||||||
import 'package:drifter/theme/app_colors.dart';
|
import 'package:drifter/theme/app_colors.dart';
|
||||||
@ -26,6 +27,7 @@ class _MainScreenWidgetState extends State<MainScreenWidget> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
backgroundColor: AppColors.mainBackground,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Row(
|
title: Row(
|
||||||
// mainAxisAlignment: MainAxisAlignment.center,
|
// mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@ -53,7 +55,7 @@ class _MainScreenWidgetState extends State<MainScreenWidget> {
|
|||||||
body: IndexedStack(
|
body: IndexedStack(
|
||||||
index: _selectedTap,
|
index: _selectedTap,
|
||||||
children: const [
|
children: const [
|
||||||
// HomeScreen(),
|
HomeScreen(),
|
||||||
MessageScreen(),
|
MessageScreen(),
|
||||||
ProfileScreen(),
|
ProfileScreen(),
|
||||||
],
|
],
|
||||||
@ -61,10 +63,10 @@ class _MainScreenWidgetState extends State<MainScreenWidget> {
|
|||||||
bottomNavigationBar: BottomNavigationBar(
|
bottomNavigationBar: BottomNavigationBar(
|
||||||
currentIndex: _selectedTap,
|
currentIndex: _selectedTap,
|
||||||
items: const [
|
items: const [
|
||||||
// BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
// icon: Icon(Icons.home),
|
icon: Icon(Icons.home),
|
||||||
// label: 'Home',
|
label: 'Home',
|
||||||
// ),
|
),
|
||||||
BottomNavigationBarItem(
|
BottomNavigationBarItem(
|
||||||
icon: Icon(Icons.message),
|
icon: Icon(Icons.message),
|
||||||
label: 'Message',
|
label: 'Message',
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import 'package:dart_nostr/dart_nostr.dart';
|
import 'package:dart_nostr/dart_nostr.dart';
|
||||||
import 'package:drifter/models/keys.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/widgets/delete_keys_dialog.dart';
|
||||||
import 'package:drifter/pages/profile_screen/profile_screen_widgets/key_exist_dialog.dart';
|
import 'package:drifter/pages/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/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/widgets/message_snack_bar.dart';
|
||||||
import 'package:drifter/pages/profile_screen/profile_screen_widgets/user_info_widget.dart';
|
import 'package:drifter/pages/profile_screen/widgets/user_info_widget.dart';
|
||||||
import 'package:drifter/theme/app_colors.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';
|
||||||
|
87
lib/pages/profile_screen/widgets/delete_keys_dialog.dart
Normal file
87
lib/pages/profile_screen/widgets/delete_keys_dialog.dart
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
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',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
128
lib/pages/profile_screen/widgets/generated_keys.dart
Normal file
128
lib/pages/profile_screen/widgets/generated_keys.dart
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
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',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
128
lib/pages/profile_screen/widgets/key_exist_dialog.dart
Normal file
128
lib/pages/profile_screen/widgets/key_exist_dialog.dart
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
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',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
46
lib/pages/profile_screen/widgets/message_snack_bar.dart
Normal file
46
lib/pages/profile_screen/widgets/message_snack_bar.dart
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
34
lib/pages/profile_screen/widgets/ok_button_widget.dart
Normal file
34
lib/pages/profile_screen/widgets/ok_button_widget.dart
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
97
lib/pages/profile_screen/widgets/user_info_widget.dart
Normal file
97
lib/pages/profile_screen/widgets/user_info_widget.dart
Normal file
@ -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: () {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
56
lib/pages/splash_screen/splash_screen.dart
Normal file
56
lib/pages/splash_screen/splash_screen.dart
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:drifter/pages/main_screen/main_screen_widget.dart';
|
||||||
|
import 'package:drifter/theme/app_colors.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class Splash extends StatefulWidget {
|
||||||
|
const Splash({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<Splash> createState() => _SplashState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SplashState extends State<Splash> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
Future.delayed(const Duration(seconds: 3), () {
|
||||||
|
Navigator.pushReplacement(context,
|
||||||
|
MaterialPageRoute(builder: (context) => const MainScreenWidget()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: AppColors.background,
|
||||||
|
body: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 300,
|
||||||
|
),
|
||||||
|
Image.asset(
|
||||||
|
'assets/images/logo/drifter_vector.png',
|
||||||
|
height: 111,
|
||||||
|
width: 93,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 250,
|
||||||
|
),
|
||||||
|
if (Platform.isAndroid)
|
||||||
|
const CircularProgressIndicator(
|
||||||
|
color: AppColors.mainAccent,
|
||||||
|
)
|
||||||
|
else
|
||||||
|
const CupertinoActivityIndicator(
|
||||||
|
radius: 20,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
abstract class AppColors {
|
abstract class AppColors {
|
||||||
static const background = const Color(0xFF4f46f1);
|
static const background = const Color(0xFF4f46f1);
|
||||||
static const mainAccent = const Color(0xFFFFCC11);
|
static const mainAccent = const Color(0xFFFFCC11);
|
||||||
|
static const mainBackground = const Color(0xFFF2EFFF);
|
||||||
|
|
||||||
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);
|
||||||
|
56
pubspec.lock
56
pubspec.lock
@ -25,6 +25,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.10.0"
|
version: "2.10.0"
|
||||||
|
base58check:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: base58check
|
||||||
|
sha256: "6c300dfc33e598d2fe26319e13f6243fea81eaf8204cb4c6b69ef20a625319a5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
bech32:
|
bech32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -33,6 +41,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.2"
|
version: "0.2.2"
|
||||||
|
bip32:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: bip32
|
||||||
|
sha256: "54787cd7a111e9d37394aabbf53d1fc5e2e0e0af2cd01c459147a97c0e3f8a97"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
bip340:
|
bip340:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -41,6 +57,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.0"
|
version: "0.1.0"
|
||||||
|
bip39:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: bip39
|
||||||
|
sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.6"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -49,6 +73,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.1"
|
||||||
|
bs58check:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: bs58check
|
||||||
|
sha256: c4a164d42b25c2f6bc88a8beccb9fc7d01440f3c60ba23663a20a70faf484ea9
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -232,6 +264,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.5"
|
version: "0.6.5"
|
||||||
|
kepler:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: kepler
|
||||||
|
sha256: "8cf9f7df525bd4e5b192d91e52f1c75832b1fefb27fb4f4a09b1412b0f4f23d0"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.3"
|
||||||
lints:
|
lints:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -264,6 +304,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.0"
|
version: "1.8.0"
|
||||||
|
nostr_tools:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: nostr_tools
|
||||||
|
sha256: "5536c4017419bcef7777f44f61884a22f864a7a937a3b10254e889ed284757d3"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.7"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -397,6 +445,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.4"
|
||||||
|
web_socket_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket_channel
|
||||||
|
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.0"
|
||||||
xml:
|
xml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -31,7 +31,7 @@ dependencies:
|
|||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
dart_nostr: ^1.5.0
|
dart_nostr: ^1.5.0
|
||||||
# nostr_tools: ^1.0.7
|
nostr_tools: ^1.0.7
|
||||||
flutter_secure_storage: ^8.0.0
|
flutter_secure_storage: ^8.0.0
|
||||||
flutter_svg: ^2.0.5
|
flutter_svg: ^2.0.5
|
||||||
|
|
||||||
@ -63,8 +63,7 @@ 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/drifter_vector.png
|
- assets/
|
||||||
- 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
|
||||||
|
Loading…
Reference in New Issue
Block a user