UsersRichText improvements, signedInUserProvider

This commit is contained in:
franzap
2025-01-05 18:17:33 -03:00
parent ea6b31f994
commit 204754920c
9 changed files with 125 additions and 114 deletions
+8
View File
@@ -247,6 +247,14 @@ mixin UserAdapter on NostrAdapter<User> {
} }
} }
final signedInUserProvider = Provider<User?>((ref) {
return ref.settings
.watchOne('_', alsoWatch: (_) => {_.user})
.model
?.user
.value;
});
const kZapstorePubkey = const kZapstorePubkey =
'78ce6faa72264387284e647ba6938995735ec8c7d5c5a65737e55130f026307d'; '78ce6faa72264387284e647ba6938995735ec8c7d5c5a65737e55130f026307d';
+11 -20
View File
@@ -29,11 +29,7 @@ class SettingsScreen extends HookConsumerWidget {
final systemInfoState = ref.watch(systemInfoNotifierProvider); final systemInfoState = ref.watch(systemInfoNotifierProvider);
final feedbackController = useTextEditingController(); final feedbackController = useTextEditingController();
final user = ref.settings final signedInUser = ref.watch(signedInUserProvider);
.watchOne('_', alsoWatch: (_) => {_.user})
.model!
.user
.value;
return SingleChildScrollView( return SingleChildScrollView(
child: Column( child: Column(
@@ -57,16 +53,17 @@ class SettingsScreen extends HookConsumerWidget {
maxLines: 10, maxLines: 10,
), ),
Gap(20), Gap(20),
if (user == null) SignInButton(minimal: true), if (signedInUser == null) SignInButton(minimal: true),
if (user != null && if (signedInUser != null &&
user.settings.value!.signInMethod != SignInMethod.nip55 || signedInUser.settings.value!.signInMethod !=
SignInMethod.nip55 ||
!amberSigner.isAvailable) !amberSigner.isAvailable)
Text( Text(
'To share feedback, you must be signed in with Amber. You can also message us on nostr!', 'To share feedback, you must be signed in with Amber. You can also message us on nostr!',
style: TextStyle( style: TextStyle(
color: Colors.red[300], fontWeight: FontWeight.bold)), color: Colors.red[300], fontWeight: FontWeight.bold)),
if (user != null && if (signedInUser != null &&
user.settings.value!.signInMethod == SignInMethod.nip55 && signedInUser.settings.value!.signInMethod == SignInMethod.nip55 &&
amberSigner.isAvailable) amberSigner.isAvailable)
AsyncButtonBuilder( AsyncButtonBuilder(
loadingWidget: SmallCircularProgressIndicator(), loadingWidget: SmallCircularProgressIndicator(),
@@ -76,7 +73,7 @@ class SettingsScreen extends HookConsumerWidget {
PartialDirectMessage( PartialDirectMessage(
content: feedbackController.text.trim(), content: feedbackController.text.trim(),
receiver: kZapstorePubkey.npub), receiver: kZapstorePubkey.npub),
withPubkey: user.pubkey); withPubkey: signedInUser.pubkey);
try { try {
final response = await http.post( final response = await http.post(
Uri.parse('https://relay.zapstore.dev/'), Uri.parse('https://relay.zapstore.dev/'),
@@ -98,15 +95,9 @@ class SettingsScreen extends HookConsumerWidget {
} }
} }
}, },
builder: (context, child, callback, state) { builder: (context, child, callback, state) =>
return switch (state) { ElevatedButton(onPressed: callback, child: child),
_ => ElevatedButton( child: Text('Send as ${signedInUser.nameOrNpub}'),
onPressed: callback,
child: child,
),
};
},
child: Text('Send as ${user.nameOrNpub}'),
), ),
Gap(20), Gap(20),
Divider(), Divider(),
+3 -9
View File
@@ -3,9 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:purplebase/purplebase.dart' as base; import 'package:purplebase/purplebase.dart' as base;
import 'package:zapstore/main.data.dart';
import 'package:zapstore/models/app.dart'; import 'package:zapstore/models/app.dart';
import 'package:zapstore/models/settings.dart';
import 'package:zapstore/models/user.dart'; import 'package:zapstore/models/user.dart';
import 'package:zapstore/widgets/sign_in_container.dart'; import 'package:zapstore/widgets/sign_in_container.dart';
import 'package:zapstore/widgets/author_container.dart'; import 'package:zapstore/widgets/author_container.dart';
@@ -22,11 +20,7 @@ class InstallAlertDialog extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final trustedSignerNotifier = useState(false); final trustedSignerNotifier = useState(false);
final user = ref.settings final signedInUser = ref.watch(signedInUserProvider);
.watchOne('_', alsoWatch: (_) => {_.user})
.model!
.user
.value;
return AlertDialog( return AlertDialog(
elevation: 10, elevation: 10,
@@ -50,10 +44,10 @@ class InstallAlertDialog extends HookConsumerWidget {
Gap(20), Gap(20),
if (app.signer.value != null) if (app.signer.value != null)
WebOfTrustContainer( WebOfTrustContainer(
fromNpub: user?.npub ?? kFranzapPubkey.npub, fromNpub: signedInUser?.npub ?? kFranzapPubkey.npub,
toNpub: app.signer.value!.npub, toNpub: app.signer.value!.npub,
), ),
if (user == null) if (signedInUser == null)
SignInButton( SignInButton(
label: 'Sign in to view your web of trust', label: 'Sign in to view your web of trust',
minimal: true, minimal: true,
+1 -5
View File
@@ -26,11 +26,7 @@ class SignInButton extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final user = ref.settings final user = ref.watch(signedInUserProvider);
.watchOne('_', alsoWatch: (_) => {_.user})
.model
?.user
.value;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
+3 -8
View File
@@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:zapstore/main.data.dart'; import 'package:zapstore/models/user.dart';
import 'package:zapstore/models/settings.dart';
import 'package:zapstore/widgets/rounded_image.dart'; import 'package:zapstore/widgets/rounded_image.dart';
class UserAvatar extends HookConsumerWidget { class UserAvatar extends HookConsumerWidget {
@@ -9,11 +8,7 @@ class UserAvatar extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final user = ref.settings final signedInUser = ref.watch(signedInUserProvider);
.watchOne('_', alsoWatch: (_) => {_.user}) return RoundedImage(url: signedInUser?.avatarUrl, size: 46);
.model!
.user
.value;
return RoundedImage(url: user?.avatarUrl, size: 46);
} }
} }
+47 -27
View File
@@ -3,55 +3,75 @@ import 'package:zapstore/models/user.dart';
import 'package:zapstore/widgets/rounded_image.dart'; import 'package:zapstore/widgets/rounded_image.dart';
class UsersRichText extends StatelessWidget { class UsersRichText extends StatelessWidget {
const UsersRichText({ final TextSpan? leadingTextSpan;
super.key,
this.leadingText,
this.trailingText,
required this.users,
});
final String? leadingText;
final String? trailingText; final String? trailingText;
final List<User> users; final List<User> users;
final User? signedInUser;
final int? maxUsersToDisplay;
final double fontSize;
const UsersRichText({
super.key,
this.leadingTextSpan,
this.trailingText,
required this.users,
this.signedInUser,
this.maxUsersToDisplay,
this.fontSize = 16,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Move signed in user to head of list
if (users.remove(signedInUser)) {
users.insert(0, signedInUser!);
}
final usersToDisplay = maxUsersToDisplay != null
? users.take(maxUsersToDisplay!).toList()
: users;
return RichText( return RichText(
text: TextSpan( text: TextSpan(
children: [ children: [
if (leadingText != null) for (final user in usersToDisplay)
TextSpan(text: leadingText!, style: TextStyle(fontSize: 15)),
for (final user in users)
TextSpan(
children: [
WidgetSpan( WidgetSpan(
alignment: PlaceholderAlignment.middle, alignment: PlaceholderAlignment.middle,
child: Wrap( child: Wrap(
spacing: 1, spacing: 0,
runSpacing: 10,
crossAxisAlignment: WrapCrossAlignment.center, crossAxisAlignment: WrapCrossAlignment.center,
children: [ children: [
RoundedImage(url: user.avatarUrl, size: 20), if (usersToDisplay.indexOf(user) == 0 &&
leadingTextSpan != null)
RichText(text: leadingTextSpan!),
if (user != signedInUser)
Padding(
padding: const EdgeInsets.only(left: 2, right: 2),
child: RoundedImage(url: user.avatarUrl, size: 20),
),
Text( Text(
' ${user.nameOrNpub}', user == signedInUser
? (leadingTextSpan != null ? ' you' : 'You')
: ' ${user.nameOrNpub}',
style: TextStyle( style: TextStyle(
fontSize: 15, fontWeight: FontWeight.bold), fontSize: fontSize, fontWeight: FontWeight.bold),
), ),
Text( Text(
users.indexOf(user) == users.length - 1 usersToDisplay.indexOf(user) == usersToDisplay.length - 1
? '' ? ''
: (users.indexOf(user) == users.length - 2 : (usersToDisplay.indexOf(user) ==
? ' and ' usersToDisplay.length - 2
? usersToDisplay.length < users.length
? ', '
: ' and '
: ', '), : ', '),
style: TextStyle(fontSize: 15), style: TextStyle(fontSize: fontSize)),
), if (usersToDisplay.indexOf(user) == usersToDisplay.length - 1)
Text(
'${usersToDisplay.length < users.length ? ' and ${users.length - usersToDisplay.length} others' : ''}${trailingText ?? ''}',
style: TextStyle(fontSize: fontSize))
], ],
), ),
), ),
if (users.indexOf(user) == users.length - 1)
TextSpan(text: trailingText, style: TextStyle(fontSize: 15))
],
),
], ],
), ),
); );
+5 -14
View File
@@ -1,10 +1,7 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:purplebase/purplebase.dart' as base;
import 'package:zapstore/main.data.dart'; import 'package:zapstore/main.data.dart';
import 'package:zapstore/models/settings.dart';
import 'package:zapstore/models/user.dart'; import 'package:zapstore/models/user.dart';
import 'package:zapstore/screens/settings_screen.dart'; import 'package:zapstore/screens/settings_screen.dart';
import 'package:zapstore/widgets/users_rich_text.dart'; import 'package:zapstore/widgets/users_rich_text.dart';
@@ -23,25 +20,20 @@ class WebOfTrustContainer extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final data = final data =
ref.watch(followsWhoFollowProvider((from: fromNpub, to: toNpub))); ref.watch(followsWhoFollowProvider((from: fromNpub, to: toNpub)));
final signedInUser = ref.watch(signedInUserProvider);
return switch (data) { return switch (data) {
AsyncData<List<User>>(value: final trustedUsers) => Builder( AsyncData<List<User>>(value: final trustedUsers) => Builder(
builder: (context) { builder: (context) {
final hasUser = if (trustedUsers.isEmpty) {
trustedUsers.firstWhereOrNull((u) => u.npub == fromNpub) !=
null;
final trustedUsersWithoutLoggedInUser =
trustedUsers.where((u) => u.npub != fromNpub).toList();
if (trustedUsersWithoutLoggedInUser.isEmpty) {
return Text( return Text(
'No trusted users between you and the signer. (This may be a service error)'); 'No trusted users between you and the signer. (This may be a service error)');
} }
return UsersRichText( return UsersRichText(
leadingText: users: trustedUsers,
hasUser && fromNpub != kFranzapPubkey.npub ? 'You, ' : null, signedInUser: signedInUser,
trailingText: ' and others follow this signer on nostr.', trailingText: ' and others follow this signer on nostr.',
users: trustedUsersWithoutLoggedInUser,
); );
}, },
), ),
@@ -66,8 +58,7 @@ class WebOfTrustContainer extends HookConsumerWidget {
final followsWhoFollowProvider = FutureProvider.autoDispose final followsWhoFollowProvider = FutureProvider.autoDispose
.family<List<User>, ({String from, String to})>((ref, arg) async { .family<List<User>, ({String from, String to})>((ref, arg) async {
final _ = final _ = ref.watch(signedInUserProvider);
ref.settings.watchOne('_', alsoWatch: (_) => {_.user}).model!.user.value;
final users = await ref.users.userAdapter.getTrusted(arg.from, arg.to); final users = await ref.users.userAdapter.getTrusted(arg.from, arg.to);
return users.toSet().toList(); return users.toSet().toList();
}); });
+18 -15
View File
@@ -1,9 +1,10 @@
import 'package:async_button_builder/async_button_builder.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:zapstore/main.data.dart'; import 'package:zapstore/main.data.dart';
import 'package:zapstore/models/app.dart'; import 'package:zapstore/models/app.dart';
import 'package:zapstore/models/settings.dart'; import 'package:zapstore/models/user.dart';
import 'package:zapstore/navigation/app_initializer.dart'; import 'package:zapstore/navigation/app_initializer.dart';
import 'package:zapstore/screens/settings_screen.dart'; import 'package:zapstore/screens/settings_screen.dart';
import 'package:zapstore/utils/extensions.dart'; import 'package:zapstore/utils/extensions.dart';
@@ -30,7 +31,16 @@ class ZapButton extends HookConsumerWidget {
return Container(); return Container();
} }
return ElevatedButton( return AsyncButtonBuilder(
loadingWidget: Text('⚡️ Zapping...').bold,
builder: (context, child, callback, state) => ElevatedButton(
onPressed: callback,
style: ElevatedButton.styleFrom(
backgroundColor: const Color.fromARGB(255, 60, 60, 54),
disabledBackgroundColor: Colors.grey[600],
),
child: child,
),
onPressed: nwcConnection.isLoading onPressed: nwcConnection.isLoading
? null ? null
: () async { : () async {
@@ -59,13 +69,10 @@ class ZapButton extends HookConsumerWidget {
children: [ children: [
Consumer( Consumer(
builder: (context, ref, _) { builder: (context, ref, _) {
final user = ref.settings final signedInUser =
.watchOne('_', alsoWatch: (_) => {_.user}) ref.watch(signedInUserProvider);
.model!
.user
.value;
if (user == null) { if (signedInUser == null) {
return Column( return Column(
children: [ children: [
Text( Text(
@@ -141,10 +148,10 @@ class ZapButton extends HookConsumerWidget {
if (amount > 0) { if (amount > 0) {
try { try {
// Reload user as we are in a callback and it could have changed // Reload user as we are in a callback and it could have changed
final loggedInUser = final signedInUser =
ref.settings.findOneLocalById('_')!.user.value; ref.settings.findOneLocalById('_')!.user.value;
if (loggedInUser != null) { if (signedInUser != null) {
await loggedInUser.zap(amount, await signedInUser.zap(amount,
event: app.latestMetadata!, comment: comment); event: app.latestMetadata!, comment: comment);
} else { } else {
await anonUser!.zap(amount, await anonUser!.zap(amount,
@@ -161,10 +168,6 @@ class ZapButton extends HookConsumerWidget {
} }
} }
}, },
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[800],
disabledBackgroundColor: Colors.grey[500],
),
child: Text( child: Text(
'⚡️ Zap this release', '⚡️ Zap this release',
style: TextStyle( style: TextStyle(
+17 -4
View File
@@ -25,6 +25,8 @@ class ZapReceipts extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final signedInUser = ref.watch(signedInUserProvider);
// NOTE: These watchers will become more efficient // NOTE: These watchers will become more efficient
ref.users.watchAll(); ref.users.watchAll();
@@ -47,14 +49,25 @@ class ZapReceipts extends HookConsumerWidget {
final receipts = zapReceipts.value!; final receipts = zapReceipts.value!;
final totalAmountInSats = receipts.fold(0, (acc, e) => acc + e.amount); final totalAmountInSats = receipts.fold(0, (acc, e) => acc + e.amount);
final senderIds = receipts.map((r) => r.senderPubkey).toSet(); final zapperIds = receipts.map((r) => r.senderPubkey).toSet();
final senders = ref.users.findManyLocalByIds(senderIds); final zappers = ref.users.findManyLocalByIds(zapperIds);
return Padding( return Padding(
padding: const EdgeInsets.only(top: 10, bottom: 10), padding: const EdgeInsets.only(top: 10, bottom: 10),
child: UsersRichText( child: UsersRichText(
leadingText: '⚡ $totalAmountInSats sats zapped from ', leadingTextSpan: TextSpan(
users: senders, style: TextStyle(fontSize: 16),
children: [
TextSpan(text: '⚡'),
TextSpan(
text: ' $totalAmountInSats sats ',
style: TextStyle(fontWeight: FontWeight.bold)),
TextSpan(text: 'zapped by'),
],
),
users: zappers,
signedInUser: signedInUser,
maxUsersToDisplay: 5,
), ),
); );
} }