diff --git a/lib/models/user.dart b/lib/models/user.dart index 2395f80..2082d1d 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -133,7 +133,7 @@ mixin UserAdapter on NostrAdapter { } Future> getTrusted(String npub1, String npub2) async { - final url = 'https://trustgraph.live/api/trust/$npub1/$npub2'; + final url = 'https://trustgraph.live/api/fwf/$npub1/$npub2'; final users = await sendRequest( Uri.parse(url), onSuccess: (response, label) async { diff --git a/lib/widgets/install_alert_dialog.dart b/lib/widgets/install_alert_dialog.dart new file mode 100644 index 0000000..1ac3076 --- /dev/null +++ b/lib/widgets/install_alert_dialog.dart @@ -0,0 +1,108 @@ +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/widgets/app_drawer.dart'; +import 'package:zapstore/widgets/author_container.dart'; +import 'package:zapstore/widgets/wot_container.dart'; + +class InstallAlertDialog extends ConsumerWidget { + const InstallAlertDialog({ + super.key, + required this.app, + }); + + final App app; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final user = ref.settings + .watchOne('_', alsoWatch: (_) => {_.user}) + .model! + .user + .value; + + return AlertDialog( + elevation: 10, + title: Text( + 'Are you sure you want to install ${app.name}?', + style: TextStyle(fontWeight: FontWeight.bold), + ), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'By installing this app you are trusting the signer now and for all future updates. Make sure you know who they are.'), + Gap(20), + if (app.signer.value != null) + AuthorContainer( + user: app.signer.value!, text: 'Signed by', oneLine: true), + Gap(20), + if (app.signer.value != null) + WebOfTrustContainer( + fromNpub: user?.npub ?? kFranzapNpub, + toNpub: app.signer.value!.npub, + ), + if (user == null) + LoginContainer( + minimal: true, + labelText: 'Log in to view your own web of trust', + ), + if (app.latestMetadata?.urls.isNotEmpty ?? false) + Padding( + padding: const EdgeInsets.only(top: 16, bottom: 16), + child: RichText( + text: WidgetSpan( + child: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + Text('This app will be downloaded from '), + Text( + Uri.parse(app.latestMetadata!.urls.first).host, + style: TextStyle( + fontWeight: FontWeight.bold, + decoration: TextDecoration.underline, + ), + ), + Text(' and verified.'), + ], + ), + ), + ), + ), + ], + ), + ), + actions: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: TextButton( + onPressed: () { + app.install(); + // NOTE: can't use context.pop() + Navigator.of(context).pop(); + }, + child: user != null + ? Text('Install', style: TextStyle(fontWeight: FontWeight.bold)) + : Text( + 'I trust the signer, install the app', + textAlign: TextAlign.right, + ), + ), + ), + TextButton( + onPressed: () { + // NOTE: can't use context.pop() + Navigator.of(context).pop(); + }, + child: Text('Go back'), + ), + ], + ); + } +} diff --git a/lib/widgets/install_button.dart b/lib/widgets/install_button.dart index 6216fba..e5879a0 100644 --- a/lib/widgets/install_button.dart +++ b/lib/widgets/install_button.dart @@ -13,6 +13,7 @@ import 'package:zapstore/utils/extensions.dart'; import 'package:zapstore/utils/system_info.dart'; import 'package:zapstore/widgets/app_drawer.dart'; import 'package:zapstore/widgets/author_container.dart'; +import 'package:zapstore/widgets/install_alert_dialog.dart'; import 'package:zapstore/widgets/spinning_logo.dart'; import 'package:zapstore/widgets/wot_container.dart'; @@ -138,102 +139,4 @@ class InstallButton extends HookConsumerWidget { } } -class InstallAlertDialog extends ConsumerWidget { - const InstallAlertDialog({ - super.key, - required this.app, - }); - - final App app; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final user = ref.settings - .watchOne('_', alsoWatch: (_) => {_.user}) - .model! - .user - .value; - return AlertDialog( - elevation: 10, - title: Text( - 'Are you sure you want to install ${app.name}?', - style: TextStyle(fontWeight: FontWeight.bold), - ), - content: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'By installing this app you are trusting the signer now and for all future updates. Make sure you know who they are.'), - Gap(20), - // SignerAndDeveloperRow(app: app), - if (app.signer.value != null) - AuthorContainer( - user: app.signer.value!, text: 'Signed by', oneLine: true), - Gap(20), - if (app.signer.value != null) - WebOfTrustContainer( - fromNpub: user?.npub ?? kFranzapNpub, - toNpub: app.signer.value!.npub, - ), - if (user == null) - LoginContainer( - minimal: true, - labelText: 'Log in to view your own web of trust', - ), - if (app.latestMetadata?.urls.isNotEmpty ?? false) - Padding( - padding: const EdgeInsets.only(top: 16, bottom: 16), - child: RichText( - text: WidgetSpan( - child: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - Text('This app will be downloaded from '), - Text( - Uri.parse(app.latestMetadata!.urls.first).host, - style: TextStyle( - fontWeight: FontWeight.bold, - decoration: TextDecoration.underline, - ), - ), - Text(' and verified.'), - ], - ), - ), - ), - ), - ], - ), - ), - actions: [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: TextButton( - onPressed: () { - app.install(); - // NOTE: can't use context.pop() - Navigator.of(context).pop(); - }, - child: user != null - ? Text('Install', style: TextStyle(fontWeight: FontWeight.bold)) - : Text( - 'I trust the signer, install the app', - textAlign: TextAlign.right, - ), - ), - ), - TextButton( - onPressed: () { - // NOTE: can't use context.pop() - Navigator.of(context).pop(); - }, - child: Text('Go back'), - ), - ], - ); - } -} - final kUpdateColor = Colors.blue[700]!; diff --git a/lib/widgets/wot_container.dart b/lib/widgets/wot_container.dart index 9c881dc..3694795 100644 --- a/lib/widgets/wot_container.dart +++ b/lib/widgets/wot_container.dart @@ -19,8 +19,10 @@ class WebOfTrustContainer extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - return switch ( - ref.watch(followsWhoFollowProvider((from: fromNpub, to: toNpub)))) { + final data = + ref.watch(followsWhoFollowProvider((from: fromNpub, to: toNpub))); + + return switch (data) { AsyncData>(value: final trustedUsers) => Builder( builder: (context) { final hasUser = @@ -32,39 +34,12 @@ class WebOfTrustContainer extends HookConsumerWidget { return Text( 'No trusted users between you and the signer. (This may be a service error)'); } - return RichText( - text: TextSpan( - children: [ - if (hasUser && fromNpub != kFranzapNpub) - TextSpan(text: 'You, '), - for (final trustedUser in trustedUsersWithoutLoggedInUser) - TextSpan( - style: TextStyle(height: 1.6), - children: [ - WidgetSpan( - alignment: PlaceholderAlignment.middle, - child: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - RoundedImage( - url: trustedUser.avatarUrl, size: 20), - Text( - ' ${trustedUser.nameOrNpub}${trustedUsersWithoutLoggedInUser.indexOf(trustedUser) == trustedUsersWithoutLoggedInUser.length - 1 ? '' : ', '}', - style: TextStyle(fontWeight: FontWeight.bold), - ), - ], - ), - ), - if (trustedUsersWithoutLoggedInUser - .indexOf(trustedUser) == - trustedUsersWithoutLoggedInUser.length - 1) - TextSpan( - text: ' and others follow this signer on nostr.', - ) - ], - ), - ], - ), + + return UsersRichText( + preSpan: hasUser && fromNpub != kFranzapNpub + ? TextSpan(text: 'You, ') + : null, + users: trustedUsersWithoutLoggedInUser, ); }, ), @@ -91,6 +66,51 @@ class WebOfTrustContainer extends HookConsumerWidget { } } +class UsersRichText extends StatelessWidget { + const UsersRichText({ + super.key, + this.preSpan, + required this.users, + }); + + final TextSpan? preSpan; + final List users; + + @override + Widget build(BuildContext context) { + return RichText( + text: TextSpan( + children: [ + if (preSpan != null) preSpan!, + for (final user in users) + TextSpan( + style: TextStyle(height: 1.6), + children: [ + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + RoundedImage(url: user.avatarUrl, size: 20), + Text( + ' ${user.nameOrNpub}${users.indexOf(user) == users.length - 1 ? '' : ', '}', + style: TextStyle(fontWeight: FontWeight.bold), + ), + ], + ), + ), + if (users.indexOf(user) == users.length - 1) + TextSpan( + text: ' and others follow this signer on nostr.', + ) + ], + ), + ], + ), + ); + } +} + final followsWhoFollowProvider = FutureProvider.autoDispose .family, ({String from, String to})>((ref, arg) async { final _ =