From 5df614159fb91dcd29903117cedf980ca391b2ee Mon Sep 17 00:00:00 2001 From: franzap <_@franzap.com> Date: Tue, 7 May 2024 22:04:27 -0300 Subject: [PATCH] Modal for wot --- lib/main.dart | 3 + lib/models/app.dart | 6 +- lib/models/user.dart | 6 +- lib/screens/app_detail_screen.dart | 148 ++++++++++++++++++++++------- lib/screens/settings_screen.dart | 82 +++++++++------- lib/widgets/app_drawer.dart | 4 + lib/widgets/card.dart | 4 + pubspec.lock | 4 +- pubspec.yaml | 2 +- 9 files changed, 182 insertions(+), 77 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index c3d2786..0963dd5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -166,6 +166,8 @@ class ScaffoldWithNestedNavigation extends HookConsumerWidget { } } +final GlobalKey scaffoldKey = GlobalKey(); + class ScaffoldWithNavigationBar extends HookConsumerWidget { const ScaffoldWithNavigationBar({ super.key, @@ -181,6 +183,7 @@ class ScaffoldWithNavigationBar extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final initializer = ref.watch(newInitializer); return Scaffold( + key: scaffoldKey, body: Padding( padding: const EdgeInsets.fromLTRB(16, 20, 16, 0), child: initializer.when( diff --git a/lib/models/app.dart b/lib/models/app.dart index 6ffad33..26b4342 100644 --- a/lib/models/app.dart +++ b/lib/models/app.dart @@ -172,9 +172,6 @@ extension AppX on App { return; } - final hash = latestMetadata!.hash!; - final size = latestMetadata!.size ?? ''; - final adapter = DataModel.adapterFor(this) as AppAdapter; final notifier = adapter.ref.read(installationProgressProvider(id!.toString()).notifier); @@ -189,6 +186,9 @@ extension AppX on App { } } + final hash = latestMetadata!.hash!; + final size = latestMetadata!.size ?? ''; + final dir = await getApplicationSupportDirectory(); final file = File(path.join(dir.path, hash)); diff --git a/lib/models/user.dart b/lib/models/user.dart index 3b48a7f..b6d70ff 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -113,7 +113,7 @@ mixin UserAdapter on NostrAdapter { DataRequestLabel? label}) async { if (id.toString().isEmpty) return null; - var publicKey = id.toString(); + String? publicKey = id.toString(); if (publicKey.startsWith('npub')) { publicKey = publicKey.hexKey; @@ -127,6 +127,10 @@ mixin UserAdapter on NostrAdapter { ); } + if (publicKey == null) { + return null; + } + final req = RelayRequest( authors: {publicKey}, kinds: {kind, if (params?['contacts'] != null) 3}, diff --git a/lib/screens/app_detail_screen.dart b/lib/screens/app_detail_screen.dart index 97d2590..d648baf 100644 --- a/lib/screens/app_detail_screen.dart +++ b/lib/screens/app_detail_screen.dart @@ -7,9 +7,11 @@ import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:intl/intl.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:zapstore/main.dart'; import 'package:zapstore/main.data.dart'; import 'package:zapstore/models/app.dart'; import 'package:zapstore/models/release.dart'; +import 'package:zapstore/services/session_service.dart'; import 'package:zapstore/utils/extensions.dart'; import 'package:zapstore/widgets/card.dart'; import 'package:zapstore/widgets/pill_widget.dart'; @@ -84,39 +86,7 @@ class AppDetailScreen extends HookConsumerWidget { Gap(10), Padding( padding: const EdgeInsets.only(right: 14), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (app.developer.isPresent) - GestureDetector( - onTap: () async { - final url = Uri.parse( - 'https://primal.net/p/${app.developer.value!.npub}'); - if (!await launchUrl(url)) { - throw Exception('Could not launch $url'); - } - }, - child: AuthorContainer( - user: app.developer.value!, - text: 'Built by', - oneLine: false), - ), - if (app.signer.isPresent) - GestureDetector( - onTap: () async { - final url = Uri.parse( - 'https://primal.net/p/${app.signer.value!.npub}'); - if (!await launchUrl(url)) { - throw Exception('Could not launch $url'); - } - }, - child: AuthorContainer( - user: app.signer.value!, - text: 'Signed by', - oneLine: false), - ), - ], - ), + child: SignerAndDeveloperRow(app: app), ), Gap(30), Container( @@ -202,6 +172,48 @@ class AppDetailScreen extends HookConsumerWidget { } } +class SignerAndDeveloperRow extends StatelessWidget { + const SignerAndDeveloperRow({ + super.key, + required this.app, + }); + + final App app; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (app.developer.isPresent) + GestureDetector( + onTap: () async { + final url = Uri.parse( + 'https://primal.net/p/${app.developer.value!.npub}'); + if (!await launchUrl(url)) { + throw Exception('Could not launch $url'); + } + }, + child: AuthorContainer( + user: app.developer.value!, text: 'Built by', oneLine: false), + ), + if (app.signer.isPresent) + GestureDetector( + onTap: () async { + final url = + Uri.parse('https://primal.net/p/${app.signer.value!.npub}'); + if (!await launchUrl(url)) { + throw Exception('Could not launch $url'); + } + }, + child: AuthorContainer( + user: app.signer.value!, text: 'Signed by', oneLine: false), + ), + ], + ); + } +} + class VersionedAppHeader extends StatelessWidget { const VersionedAppHeader({ super.key, @@ -356,7 +368,19 @@ class InstallButton extends ConsumerWidget { LaunchApp.openApp(androidPackageName: app.id!.toString()); }, _ => switch (progress) { - IdleInstallProgress() => () => app.install(), + IdleInstallProgress() => () { + // show trust dialog only if first install + if (app.canInstall) { + showDialog( + context: context, + builder: (BuildContext context) { + return InstallAlertDialog(app: app); + }, + ); + } else if (app.canUpdate) { + app.install(); + } + }, ErrorInstallProgress(e: final e) => () { // show error and reset state to idle context.showError(e.toString()); @@ -402,3 +426,59 @@ class InstallButton extends ConsumerWidget { ); } } + +class InstallAlertDialog extends ConsumerWidget { + const InstallAlertDialog({ + super.key, + required this.app, + }); + + final App app; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return AlertDialog( + elevation: 10, + title: Text( + 'Are you sure you want to install ${app.name}?', + style: TextStyle(fontWeight: FontWeight.bold), + ), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'By installing this app you are trusting the signer and the developer now and for all future updates. Make sure you know who they are.'), + Gap(20), + SignerAndDeveloperRow(app: app), + // AppDrawer(), + ], + ), + actions: [ + if (ref.read(loggedInUser) != null) + TextButton( + onPressed: () { + app.install(); + // NOTE: can't use context.pop() + Navigator.of(context).pop(); + }, + child: Text('Install'), + ), + if (ref.read(loggedInUser) == null) + TextButton( + onPressed: () { + Navigator.of(context).pop(); + scaffoldKey.currentState!.openDrawer(); + }, + child: Text('Log in to view web of trust'), + ), + TextButton( + onPressed: () { + // NOTE: can't use context.pop() + Navigator.of(context).pop(); + }, + child: Text('Go back'), + ), + ], + ); + } +} diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 0926b0d..c1af4ca 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -15,42 +15,52 @@ class SettingsScreen extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final controller = useTextEditingController(); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Share your feedback', - style: context.theme.textTheme.headlineLarge, - ), - Gap(10), - Text('Comments, suggestions and error reports welcome here.'), - Gap(20), - TextField( - controller: controller, - autofocus: true, - maxLines: 10, - ), - Gap(20), - AsyncButtonBuilder( - loadingWidget: SizedBox( - width: 14, height: 14, child: CircularProgressIndicator()), - onPressed: () async { - final user = ref.read(loggedInUser); - final text = '${controller.text.trim()} [from ${user?.npub}]'; - final event = BaseEvent.partial(content: text).sign(kI); - // TODO only send to relay.zap.store? - await ref.apps.nostrAdapter.notifier.publish(event); - controller.clear(); - }, - builder: (context, child, callback, buttonState) { - return ElevatedButton( - onPressed: callback, - child: child, - ); - }, - child: Text('Send'), - ), - ], + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Share your feedback', + style: context.theme.textTheme.headlineLarge! + .copyWith(fontWeight: FontWeight.bold), + ), + Gap(10), + Text('Comments, suggestions and error reports welcome here.'), + Gap(20), + TextField( + controller: controller, + autofocus: true, + maxLines: 10, + ), + Gap(20), + AsyncButtonBuilder( + loadingWidget: SizedBox( + width: 14, height: 14, child: CircularProgressIndicator()), + onPressed: () async { + final user = ref.read(loggedInUser); + final text = '${controller.text.trim()} [from ${user?.npub}]'; + final event = BaseEvent.partial(content: text).sign(kI); + await ref.apps.nostrAdapter.notifier + .publish(event, relayUrls: ['wss://relay.zap.store']); + controller.clear(); + }, + builder: (context, child, callback, buttonState) { + return ElevatedButton( + onPressed: callback, + child: child, + ); + }, + child: Text('Send'), + ), + Divider(), + Gap(30), + Text( + 'How does zap.store work', + style: context.theme.textTheme.headlineLarge! + .copyWith(fontWeight: FontWeight.bold), + ), + ], + ), ); } } diff --git a/lib/widgets/app_drawer.dart b/lib/widgets/app_drawer.dart index e692d65..6ab6515 100644 --- a/lib/widgets/app_drawer.dart +++ b/lib/widgets/app_drawer.dart @@ -88,6 +88,10 @@ class AppDrawer extends HookConsumerWidget { }, child: Text('Log out'), ), + Gap(20), + if (user == null) + Text( + 'Feel free to log in with fran@zap.store if you don\'t want to use yours.'), ], ), ], diff --git a/lib/widgets/card.dart b/lib/widgets/card.dart index c0bbac8..2ce0f5f 100644 --- a/lib/widgets/card.dart +++ b/lib/widgets/card.dart @@ -168,6 +168,10 @@ class CircularImage extends StatelessWidget { height: size, width: size, color: Colors.grey[800], + child: Icon( + Icons.person, + color: Colors.blueGrey, + ), ); return ClipRRect( borderRadius: BorderRadius.circular(radius.toDouble()), diff --git a/pubspec.lock b/pubspec.lock index 40ce509..675bb7e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -816,8 +816,8 @@ packages: dependency: "direct main" description: path: "." - ref: "617e018201a875281b7c484bcd79ead7fea078d6" - resolved-ref: "617e018201a875281b7c484bcd79ead7fea078d6" + ref: f6a6a7 + resolved-ref: f6a6a71c5fb791e9eb82e2678ba5e5abaeb01462 url: "https://github.com/purplebase/purplebase" source: git version: "0.1.0" diff --git a/pubspec.yaml b/pubspec.yaml index 5bc622e..1405fd3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -57,7 +57,7 @@ dependency_overrides: purplebase: git: url: https://github.com/purplebase/purplebase - ref: 617e018201a875281b7c484bcd79ead7fea078d6 + ref: f6a6a7 flutter_data: path: ../flutter_data