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