mirror of
https://github.com/zapstore/zapstore.git
synced 2026-10-05 12:38:24 +00:00
UsersRichText improvements, signedInUserProvider
This commit is contained in:
@@ -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';
|
||||
|
||||
|
||||
@@ -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 +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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,54 +3,74 @@ 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: [
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Wrap(
|
||||
spacing: 1,
|
||||
runSpacing: 10,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
RoundedImage(url: user.avatarUrl, size: 20),
|
||||
Text(
|
||||
' ${user.nameOrNpub}',
|
||||
style: TextStyle(
|
||||
fontSize: 15, fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(
|
||||
users.indexOf(user) == users.length - 1
|
||||
? ''
|
||||
: (users.indexOf(user) == users.length - 2
|
||||
? ' and '
|
||||
: ', '),
|
||||
style: TextStyle(fontSize: 15),
|
||||
),
|
||||
],
|
||||
for (final user in usersToDisplay)
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Wrap(
|
||||
spacing: 0,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
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 == signedInUser
|
||||
? (leadingTextSpan != null ? ' you' : 'You')
|
||||
: ' ${user.nameOrNpub}',
|
||||
style: TextStyle(
|
||||
fontSize: fontSize, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
if (users.indexOf(user) == users.length - 1)
|
||||
TextSpan(text: trailingText, style: TextStyle(fontSize: 15))
|
||||
],
|
||||
Text(
|
||||
usersToDisplay.indexOf(user) == usersToDisplay.length - 1
|
||||
? ''
|
||||
: (usersToDisplay.indexOf(user) ==
|
||||
usersToDisplay.length - 2
|
||||
? usersToDisplay.length < users.length
|
||||
? ', '
|
||||
: ' and '
|
||||
: ', '),
|
||||
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))
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user