From 9799d7f0c67e65055d28d499bd78462d46859148 Mon Sep 17 00:00:00 2001 From: franzap <_@franzap.com> Date: Thu, 20 Mar 2025 22:37:10 -0300 Subject: [PATCH] Developer screen WIP, remove concept of developer and just refer to signer as developer --- lib/models/app.dart | 24 ++--- lib/models/app.g.dart | 14 +-- lib/models/nostr_adapter.dart | 6 +- lib/models/user.dart | 5 + lib/models/user.g.dart | 14 +++ lib/navigation/router.dart | 28 +++--- lib/screens/app_detail_screen.dart | 19 +++- lib/screens/developer_screen.dart | 91 +++++++++++++++++++ lib/screens/updates_screen.dart | 9 +- lib/widgets/app_card.dart | 12 +-- .../relevant_who_follow_container.dart | 18 +++- lib/widgets/signer_and_developer_row.dart | 60 ------------ lib/widgets/signer_container.dart | 50 ++++++++++ lib/widgets/zap_button.dart | 2 +- lib/widgets/zap_receipts.dart | 75 +++++---------- 15 files changed, 240 insertions(+), 187 deletions(-) create mode 100644 lib/screens/developer_screen.dart delete mode 100644 lib/widgets/signer_and_developer_row.dart create mode 100644 lib/widgets/signer_container.dart diff --git a/lib/models/app.dart b/lib/models/app.dart index bd14c2f..e5b329c 100644 --- a/lib/models/app.dart +++ b/lib/models/app.dart @@ -34,12 +34,10 @@ class App extends base.App with DataModelMixin { final HasMany releases; final BelongsTo signer; - final BelongsTo developer; final BelongsTo localApp; App.fromJson(super.map) - : developer = belongsTo(map['developer']), - releases = hasMany(map['releases']), + : releases = hasMany(map['releases']), signer = belongsTo(map['signer']), localApp = belongsTo(map['localApp']), super.fromJson(); @@ -74,6 +72,9 @@ class App extends base.App with DataModelMixin { bool get hasCertificateMismatch => localApp.value?.status == AppInstallStatus.certificateMismatch; + bool get isSelfSigned => !(signer.value!.pubkey == kZapstorePubkey && + identifier != kZapstoreAppIdentifier); + Future install({bool alwaysTrustSigner = false}) async { if (!canInstall && !canUpdate || hasCertificateMismatch) { throw Exception( @@ -221,7 +222,7 @@ mixin AppAdapter on Adapter { // If looking up multiple apps via `#d`, use `queriedAtMap` // to find the earliest queried at Date, to be used in the // next request `since` field - if (params.containsKey('#d') && !params['skipCache']) { + if (params.containsKey('#d') && !(params['skipCache'] ?? false)) { DateTime? earliestQueryAt; final identifiers = {...params['#d']}; @@ -348,21 +349,8 @@ mixin AppAdapter on Adapter { for (final Map map in list) { final tags = map['tags']; - final pubkey = map['pubkey']; map['localApp'] = base.BaseUtil.getTag(tags, 'd')!; - - // Find developer - final zapTag = base.BaseUtil.getTag(tags, 'zap'); - if (pubkey == kZapstorePubkey) { - if (zapTag != null) { - map['developer'] = zapTag; - } - if (map['localApp'] == kZapstoreAppIdentifier) { - map['developer'] = pubkey; - } - } else { - map['developer'] = pubkey; - } + map['signer'] = map['pubkey']; } return super.deserialize(data); diff --git a/lib/models/app.g.dart b/lib/models/app.g.dart index ae662c8..b0cfc0a 100644 --- a/lib/models/app.g.dart +++ b/lib/models/app.g.dart @@ -19,16 +19,11 @@ mixin _$AppAdapter on Adapter { ), 'signer': RelationshipMeta( name: 'signer', + inverseName: 'apps', type: 'users', kind: 'BelongsTo', instance: (_) => (_ as App).signer, ), - 'developer': RelationshipMeta( - name: 'developer', - type: 'users', - kind: 'BelongsTo', - instance: (_) => (_ as App).developer, - ), 'localApp': RelationshipMeta( name: 'localApp', type: 'localApps', @@ -81,13 +76,6 @@ extension AppRelationshipGraphNodeX on RelationshipGraphNode { parent: this is RelationshipMeta ? this as RelationshipMeta : null); } - RelationshipGraphNode get developer { - final meta = _$AppAdapter._kAppRelationshipMetas['developer'] - as RelationshipMeta; - return meta.clone( - parent: this is RelationshipMeta ? this as RelationshipMeta : null); - } - RelationshipGraphNode get localApp { final meta = _$AppAdapter._kAppRelationshipMetas['localApp'] as RelationshipMeta; diff --git a/lib/models/nostr_adapter.dart b/lib/models/nostr_adapter.dart index 1adc0ca..51c65f4 100644 --- a/lib/models/nostr_adapter.dart +++ b/lib/models/nostr_adapter.dart @@ -164,11 +164,7 @@ mixin NostrAdapter> on Adapter { final metadataIds = releases.map((r) => r.event.linkedEvents).nonNulls.expand((_) => _); final apps = releases.map((r) => r.app.value).nonNulls; - - final userIds = { - for (final app in apps) app.signer.id, - for (final app in apps) app.developer.id, - }.nonNulls; + final userIds = apps.map((a) => a.signer.id).nonNulls; // Metadata and users probably go to separate relays // so query in parallel diff --git a/lib/models/user.dart b/lib/models/user.dart index 87ccc8b..788b750 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -6,6 +6,7 @@ import 'package:flutter_data/flutter_data.dart'; import 'package:purplebase/purplebase.dart' as base; import 'package:http/http.dart' as http; import 'package:zapstore/main.data.dart'; +import 'package:zapstore/models/app.dart'; import 'package:zapstore/models/nostr_adapter.dart'; import 'package:zapstore/models/settings.dart'; import 'package:zapstore/models/verify_reputation_dvm.dart'; @@ -24,6 +25,7 @@ class User extends base.User with DataModelMixin { : followers = hasMany(map['followers']), following = hasMany(map['following']), settings = belongsTo(map['settings']), + apps = hasMany(map['apps']), super.fromJson(); User.fromPubkey(String pubkey) @@ -45,6 +47,9 @@ class User extends base.User with DataModelMixin { @DataRelationship(inverse: 'user') final BelongsTo settings; + @DataRelationship(inverse: 'signer') + final HasMany apps; + String get nameOrNpub => name ?? npub.shorten; // Zaps diff --git a/lib/models/user.g.dart b/lib/models/user.g.dart index a2b3429..53e9636 100644 --- a/lib/models/user.g.dart +++ b/lib/models/user.g.dart @@ -30,6 +30,13 @@ mixin _$UserAdapter on Adapter { type: 'settings', kind: 'BelongsTo', instance: (_) => (_ as User).settings, + ), + 'apps': RelationshipMeta( + name: 'apps', + inverseName: 'signer', + type: 'apps', + kind: 'HasMany', + instance: (_) => (_ as User).apps, ) }; @@ -84,4 +91,11 @@ extension UserRelationshipGraphNodeX on RelationshipGraphNode { return meta.clone( parent: this is RelationshipMeta ? this as RelationshipMeta : null); } + + RelationshipGraphNode get apps { + final meta = + _$UserAdapter._kUserRelationshipMetas['apps'] as RelationshipMeta; + return meta.clone( + parent: this is RelationshipMeta ? this as RelationshipMeta : null); + } } diff --git a/lib/navigation/router.dart b/lib/navigation/router.dart index f454c01..e1c4172 100644 --- a/lib/navigation/router.dart +++ b/lib/navigation/router.dart @@ -4,10 +4,12 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:zapstore/main.dart'; import 'package:zapstore/models/app.dart'; import 'package:zapstore/models/nostr_adapter.dart'; +import 'package:zapstore/models/user.dart'; import 'package:zapstore/navigation/app_initializer.dart'; import 'package:zapstore/navigation/desktop_scaffold.dart'; import 'package:zapstore/navigation/mobile_scaffold.dart'; import 'package:zapstore/screens/app_detail_screen.dart'; +import 'package:zapstore/screens/developer_screen.dart'; import 'package:zapstore/screens/search_screen.dart'; import 'package:zapstore/screens/settings_screen.dart'; import 'package:zapstore/screens/updates_screen.dart'; @@ -37,13 +39,7 @@ final appRouter = GoRouter( GoRoute( path: '/', builder: (context, state) => SearchScreen(), - routes: [ - GoRoute( - path: 'details', - builder: (context, state) => - AppDetailScreen(model: state.extra as App), - ), - ], + routes: [appDetailsRoute, developerRoute], ), ], ), @@ -53,13 +49,7 @@ final appRouter = GoRouter( GoRoute( path: '/updates', builder: (context, state) => UpdatesScreen(), - routes: [ - GoRoute( - path: 'details', - builder: (context, state) => - AppDetailScreen(model: state.extra as App), - ), - ], + routes: [appDetailsRoute, developerRoute], ), ], ), @@ -79,6 +69,16 @@ final appRouter = GoRouter( ], ); +final appDetailsRoute = GoRoute( + path: 'details', + builder: (context, state) => AppDetailScreen(model: state.extra as App), +); + +final developerRoute = GoRoute( + path: 'developer', + builder: (context, state) => DeveloperScreen(model: state.extra as User), +); + class ScaffoldWithNestedNavigation extends HookConsumerWidget { const ScaffoldWithNestedNavigation({ Key? key, diff --git a/lib/screens/app_detail_screen.dart b/lib/screens/app_detail_screen.dart index 0c0f374..840aa09 100644 --- a/lib/screens/app_detail_screen.dart +++ b/lib/screens/app_detail_screen.dart @@ -14,9 +14,10 @@ import 'package:zapstore/models/release.dart'; import 'package:zapstore/utils/extensions.dart'; import 'package:zapstore/widgets/install_button.dart'; import 'package:zapstore/widgets/release_card.dart'; -import 'package:zapstore/widgets/signer_and_developer_row.dart'; +import 'package:zapstore/widgets/signer_container.dart'; import 'package:zapstore/widgets/users_rich_text.dart'; import 'package:zapstore/widgets/versioned_app_header.dart'; +import 'package:zapstore/widgets/zap_button.dart'; import 'package:zapstore/widgets/zap_receipts.dart'; class AppDetailScreen extends HookConsumerWidget { @@ -43,7 +44,6 @@ class AppDetailScreen extends HookConsumerWidget { _.releases, _.releases.artifacts, _.signer, - _.developer }); final app = state.model ?? model; @@ -166,9 +166,20 @@ class AppDetailScreen extends HookConsumerWidget { Gap(10), Padding( padding: const EdgeInsets.only(right: 14), - child: SignerAndDeveloperRow(app: app), + child: SignerContainer(app: app), ), - ZapReceipts(app: app), + if (app.latestMetadata != null && app.isSelfSigned) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Gap(10), + SizedBox( + width: double.infinity, + child: ZapButton(app: app)), + Gap(10), + ZapReceipts(app: app), + ], + ), Gap(20), Container( padding: EdgeInsets.all(10), diff --git a/lib/screens/developer_screen.dart b/lib/screens/developer_screen.dart new file mode 100644 index 0000000..d75ed6c --- /dev/null +++ b/lib/screens/developer_screen.dart @@ -0,0 +1,91 @@ +import 'package:auto_size_text/auto_size_text.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_data/flutter_data.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:gap/gap.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:zapstore/main.data.dart'; +import 'package:zapstore/models/user.dart'; +import 'package:zapstore/widgets/app_card.dart'; +import 'package:zapstore/widgets/author_container.dart'; +import 'package:zapstore/widgets/relevant_who_follow_container.dart'; +import 'package:zapstore/widgets/rounded_image.dart'; +import 'package:zapstore/widgets/user_avatar.dart'; +import 'package:zapstore/widgets/zap_receipts.dart'; + +class DeveloperScreen extends HookConsumerWidget { + final User model; + + DeveloperScreen({ + required this.model, + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + useFuture(useMemoized(() async { + if (model.pubkey != kZapstorePubkey) { + await ref.apps.findAll( + params: { + 'authors': [model.pubkey], + 'ignoreReturn': false, + }, + ); + } + })); + + final state = ref.users.watchOne(model.id!, alsoWatch: (_) => {_.apps}); + + return SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + RoundedImage(url: model.avatarUrl, size: 80), + Gap(10), + Expanded( + child: AutoSizeText( + model.nameOrNpub, + style: TextStyle(fontSize: 34, fontWeight: FontWeight.bold), + ), + ), + ], + ), + Gap(20), + // if (model.apps.first.latestMetadata != null) + // ZapReceipts(developer: model), + RelevantWhoFollowContainer( + toNpub: model.npub, + loadingText: 'Loading connections...', + ), + Gap(10), + ElevatedButton( + onPressed: () { + final url = Uri.parse('https://npub.world/${model.npub}'); + launchUrl(url); + }, + child: Text( + 'View ${model.nameOrNpub} on nostr', + ), + ), + Gap(20), + Text('Signed apps', + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)), + Gap(10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (state.hasModel) + for (final app in model.apps.where((a) => a.isSelfSigned)) + AppCard(model: app, showUpdate: true), + ], + ), + ], + ), + ); + } +} diff --git a/lib/screens/updates_screen.dart b/lib/screens/updates_screen.dart index 08ae3a1..9f9dd8a 100644 --- a/lib/screens/updates_screen.dart +++ b/lib/screens/updates_screen.dart @@ -23,13 +23,8 @@ class UpdatesScreen extends HookConsumerWidget { key: UniqueKey(), crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text('Installed apps', - style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold)), - ], - ), + Text('Installed apps', + style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold)), if (updatedApps.isEmpty && updatableApps.isEmpty) SpinningLogo(size: 80), if (updatableApps.isNotEmpty) diff --git a/lib/widgets/app_card.dart b/lib/widgets/app_card.dart index e383f4b..5f030d3 100644 --- a/lib/widgets/app_card.dart +++ b/lib/widgets/app_card.dart @@ -7,7 +7,6 @@ import 'package:remove_markdown/remove_markdown.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:zapstore/main.data.dart'; import 'package:zapstore/models/app.dart'; -import 'package:zapstore/models/user.dart'; import 'package:zapstore/utils/extensions.dart'; import 'package:zapstore/widgets/author_container.dart'; import 'package:zapstore/widgets/install_button.dart'; @@ -33,9 +32,7 @@ class AppCard extends HookConsumerWidget { final isUpdate = app.canUpdate && showUpdate; return GestureDetector( - onTap: () { - context.go('${showUpdate ? '/updates' : ''}/details', extra: app); - }, + onTap: () => context.push('/details', extra: app), child: Card( margin: EdgeInsets.only(top: 6, bottom: 6), elevation: 0, @@ -82,10 +79,7 @@ class AppCard extends HookConsumerWidget { softWrap: true, ), Gap(6), - // Only show Signed by apps' original authors - if (app.signer.isPresent && - (app.signer.value!.pubkey != kZapstorePubkey || - {kZapstoreAppIdentifier}.contains(app.identifier))) + if (app.signer.isPresent && app.isSelfSigned) AuthorContainer( user: app.signer.value!, beforeText: 'Signed by', @@ -186,7 +180,7 @@ class TinyAppCard extends HookConsumerWidget { return GestureDetector( onTap: () { if (app != null) { - context.go('/details', extra: app); + context.push('/details', extra: app); } }, child: Card( diff --git a/lib/widgets/relevant_who_follow_container.dart b/lib/widgets/relevant_who_follow_container.dart index ec10342..357a0bd 100644 --- a/lib/widgets/relevant_who_follow_container.dart +++ b/lib/widgets/relevant_who_follow_container.dart @@ -1,6 +1,7 @@ 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/user.dart'; import 'package:zapstore/screens/settings_screen.dart'; @@ -10,8 +11,10 @@ class RelevantWhoFollowContainer extends HookConsumerWidget { const RelevantWhoFollowContainer({ super.key, required this.toNpub, + this.loadingText = 'Checking reputation...', }); + final String loadingText; final String toNpub; @override @@ -19,16 +22,23 @@ class RelevantWhoFollowContainer extends HookConsumerWidget { final signedInUser = ref.watch(signedInUserProvider); final data = ref.watch(relevantWhoFollowProvider((to: toNpub))); + if (signedInUser == null) { + return Container(); + } + return switch (data) { - AsyncData>(value: final trustedUsers) => Builder( + AsyncData>(value: final users) => Builder( builder: (context) { - if (trustedUsers.isEmpty) { + // Find relevant who follow, remove the target that comes included + final relevantWhoFollow = + users.where((u) => u.pubkey.npub != toNpub).toList(); + if (relevantWhoFollow.isEmpty) { return Text( 'No reputable profiles follow the signer. Make sure you know the signer.'); } return UsersRichText( - users: trustedUsers, + users: relevantWhoFollow, signedInUser: signedInUser, onlyUseCommaSeparator: true, trailingText: ' and others follow this signer on nostr.', @@ -42,7 +52,7 @@ class RelevantWhoFollowContainer extends HookConsumerWidget { padding: const EdgeInsets.all(24), child: Column( children: [ - Text('Checking reputation...'), + Text(loadingText), Gap(10), SmallCircularProgressIndicator(), ], diff --git a/lib/widgets/signer_and_developer_row.dart b/lib/widgets/signer_and_developer_row.dart deleted file mode 100644 index 332c403..0000000 --- a/lib/widgets/signer_and_developer_row.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:url_launcher/url_launcher.dart'; -import 'package:zapstore/main.data.dart'; -import 'package:zapstore/models/app.dart'; -import 'package:zapstore/widgets/author_container.dart'; - -class SignerAndDeveloperRow extends ConsumerWidget { - const SignerAndDeveloperRow({ - super.key, - required this.app, - }); - - final App app; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final settingsState = ref.settings.watchOne('_', remote: false); - - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (app.developer.isPresent) - GestureDetector( - onTap: () async { - final url = - Uri.parse('https://njump.me/${app.developer.value!.npub}'); - launchUrl(url); - }, - child: AuthorContainer( - user: app.developer.value!, - beforeText: 'Developed by', - oneLine: false), - ), - if (app.signer.isPresent) - GestureDetector( - onTap: () async { - final url = - Uri.parse('https://njump.me/${app.signer.value!.npub}'); - launchUrl(url); - }, - child: Row( - children: [ - AuthorContainer( - user: app.signer.value!, - beforeText: 'Signed by', - oneLine: false, - afterText: settingsState.hasModel && - settingsState.model!.trustedUsers - .contains(app.signer.value!) - ? '(trusted)' - : null, - ), - ], - ), - ), - ], - ); - } -} diff --git a/lib/widgets/signer_container.dart b/lib/widgets/signer_container.dart new file mode 100644 index 0000000..b5d46d9 --- /dev/null +++ b/lib/widgets/signer_container.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:zapstore/main.data.dart'; +import 'package:zapstore/models/app.dart'; +import 'package:zapstore/widgets/author_container.dart'; + +class SignerContainer extends ConsumerWidget { + const SignerContainer({ + super.key, + required this.app, + }); + + final App app; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final settingsState = ref.settings.watchOne('_', remote: false); + + if (!app.signer.isPresent) { + return Container(); + } + + return GestureDetector( + onTap: () async { + if (app.isSelfSigned) { + context.push('/developer', extra: app.signer.value); + } else { + final url = Uri.parse('https://npub.world/${app.signer.value!.npub}'); + launchUrl(url); + } + }, + child: Row( + children: [ + AuthorContainer( + user: app.signer.value!, + beforeText: 'Signed by', + oneLine: false, + afterText: settingsState.hasModel && + settingsState.model!.trustedUsers + .contains(app.signer.value!) + ? '(trusted)' + : null, + ), + ], + ), + ); + } +} diff --git a/lib/widgets/zap_button.dart b/lib/widgets/zap_button.dart index 63b985b..c6029ef 100644 --- a/lib/widgets/zap_button.dart +++ b/lib/widgets/zap_button.dart @@ -30,7 +30,7 @@ class ZapButton extends HookConsumerWidget { final amountController = TextEditingController(); final commentController = TextEditingController(); - if (app.developer.value?.lud16 == null) { + if (app.signer.value?.lud16 == null) { return Container(); } diff --git a/lib/widgets/zap_receipts.dart b/lib/widgets/zap_receipts.dart index 8a93858..2b28c66 100644 --- a/lib/widgets/zap_receipts.dart +++ b/lib/widgets/zap_receipts.dart @@ -16,7 +16,6 @@ import 'package:zapstore/models/zap_receipt.dart'; import 'package:zapstore/screens/settings_screen.dart'; import 'package:zapstore/utils/extensions.dart'; import 'package:zapstore/widgets/users_rich_text.dart'; -import 'package:zapstore/widgets/zap_button.dart'; class ZapReceipts extends HookConsumerWidget { ZapReceipts({ @@ -33,30 +32,21 @@ class ZapReceipts extends HookConsumerWidget { // NOTE: These watchers will become more efficient ref.users.watchAll(); - final fileMetadata = app.latestMetadata; - // NOTE: Only show zaps for self-signed apps (at least for now) - final isSelfSigned = app.signer.value == app.developer.value; - if (fileMetadata == null || !isSelfSigned) { - return Container(); - } - - final zapReceipts = ref.watch(zapReceiptsNotifier(fileMetadata)); + final zapReceipts = ref.watch(zapReceiptsNotifier(app.latestMetadata!)); if (zapReceipts.isLoading) { - return _wrap( - Row( - children: [ - Gap(4), - Text('Loading zaps...'), - Gap(10), - SmallCircularProgressIndicator(), - ], - ), + return Row( + children: [ + Gap(4), + Text('Loading zaps...'), + Gap(10), + SmallCircularProgressIndicator(), + ], ); } if (zapReceipts.value!.isEmpty) { - return _wrap(Container()); + return Container(); } final receipts = zapReceipts.value!; @@ -75,36 +65,20 @@ class ZapReceipts extends HookConsumerWidget { return totalUserAmount.wrapped; }); - return _wrap( - UsersRichText( - leadingTextSpan: TextSpan( - style: TextStyle(fontSize: 16, height: 1.7), - children: [ - TextSpan(text: '⚡'), - TextSpan( - text: ' $formattedSatsAmount sats ', - style: TextStyle(fontWeight: FontWeight.bold)), - TextSpan(text: 'zapped by'), - ], - ), - users: zappers, - signedInUser: signedInUser, - maxUsersToDisplay: kMaxUsersToDisplay, - ), - ); - } - - Widget _wrap(Widget inner) { - return Padding( - padding: const EdgeInsets.only(top: 10, bottom: 10), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 10, + return UsersRichText( + leadingTextSpan: TextSpan( + style: TextStyle(fontSize: 16, height: 1.7), children: [ - SizedBox(width: double.maxFinite, child: ZapButton(app: app)), - inner, + TextSpan(text: '⚡'), + TextSpan( + text: ' $formattedSatsAmount sats ', + style: TextStyle(fontWeight: FontWeight.bold)), + TextSpan(text: 'zapped by'), ], ), + users: zappers, + signedInUser: signedInUser, + maxUsersToDisplay: kMaxUsersToDisplay, ); } } @@ -119,13 +93,10 @@ class ZapReceiptsNotifier extends StateNotifier>> { ZapReceiptsNotifier(this.ref, this.fileMetadata) : super(AsyncLoading()) { final adapter = ref.users.nostrAdapter; - final developerPubkey = - fileMetadata.event.getTag('zap') ?? fileMetadata.event.pubkey; + final developerPubkey = fileMetadata.event.pubkey; + final app = fileMetadata.release.value!.app.value!; - // If it is a Zapstore-signed release from the indexer, return - if (developerPubkey == kZapstorePubkey && - fileMetadata.release.value!.app.value!.identifier != - kZapstoreAppIdentifier) { + if (!app.isSelfSigned) { state = AsyncData({}); return; }