Files
zapstore/lib/widgets/install_alert_dialog.dart
T

156 lines
5.7 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:models/models.dart';
import 'package:zapstore/utils/extensions.dart';
import 'package:zapstore/widgets/author_container.dart';
import 'package:zapstore/widgets/common/base_dialog.dart';
import 'package:zapstore/widgets/download_text_container.dart';
import 'package:zapstore/widgets/relevant_who_follow_container.dart';
import 'package:zapstore/widgets/sign_in_button.dart';
class InstallAlertDialog extends HookConsumerWidget {
const InstallAlertDialog({super.key, required this.app});
final App app;
@override
Widget build(BuildContext context, WidgetRef ref) {
// Query publisher profile (app.pubkey is always present)
final publisherState = ref.watch(
query<Profile>(
authors: {app.pubkey},
source: const LocalAndRemoteSource(
relays: {'social', 'vertex'},
cachedFor: Duration(hours: 2),
),
subscriptionPrefix: 'app-install-profile',
),
);
// Kind-0 is optional — only spin while the query is in flight.
// Settled empty/error must still allow install (cold release keys).
final publisher = publisherState.models.firstOrNull;
final isPublisherLoading =
publisherState is StorageLoading && publisher == null;
final trustedSignerNotifier = useState(false);
final signedInPubkey = ref.watch(Signer.activePubkeyProvider);
final activeSigner = ref.watch(Signer.activeSignerProvider);
final canPersistTrust = signedInPubkey != null && activeSigner != null;
final theme = Theme.of(context);
final baseTextSize = theme.textTheme.bodyMedium?.fontSize ?? 14.0;
return BaseDialog(
title: const BaseDialogTitle('Trust this app?'),
content: BaseDialogContent(
children: [
if (app.isRelaySigned) ...[
AuthorContainer(
profile: publisher,
pubkey: app.pubkey,
isLoading: isPublisherLoading,
beforeText: 'The',
afterText:
' relay makes the ${app.name ?? app.identifier} app available but did not develop it or sign its APK. ',
oneLine: false,
size: baseTextSize,
),
if (app.installable?.urls.firstOrNull != null) ...[
Gap(10),
DownloadTextContainer(
beforeText:
'${app.name ?? app.identifier} will be installed from its original release location:',
oneLine: false,
showFullUrl: true,
url: app.installable!.urls.first,
size: baseTextSize,
),
],
] else ...[
canPersistTrust
? RelevantWhoFollowContainer(app: app, size: baseTextSize)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AuthorContainer(
profile: publisher,
pubkey: app.pubkey,
isLoading: isPublisherLoading,
beforeText:
'${app.name ?? app.identifier} is published by',
afterText: '.',
oneLine: false,
size: baseTextSize,
),
const Gap(14),
SignInButton(
label:
'Sign in to view the publisher\'s reputable followers',
minimal: true,
requireNip55: true,
),
],
),
// Only show "Always trust" when signed in (requires signer to persist)
if (canPersistTrust) ...[
const Gap(14),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Switch(
value: trustedSignerNotifier.value,
onChanged: (value) {
trustedSignerNotifier.value = value;
},
),
Gap(4),
Expanded(
child: Row(
children: [
Text(
'Always trust',
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurface,
),
),
Gap(4),
AuthorContainer(
beforeText: '',
profile: publisher,
pubkey: app.pubkey,
isLoading: isPublisherLoading,
size: baseTextSize,
),
],
),
),
],
),
],
],
],
),
actions: [
TextButton(
onPressed: () {
// NOTE: can't use context.pop()
Navigator.of(context).pop();
},
child: const Text('Cancel'),
),
FilledButton(
onPressed: () {
Navigator.of(
context,
).pop((trustPermanently: trustedSignerNotifier.value));
},
style: FilledButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
child: const Text('Trust and install app'),
),
],
);
}
}