From f4c4ef2475d287895b59932cf107fd3275113996 Mon Sep 17 00:00:00 2001 From: franzap <_@franzap.com> Date: Tue, 22 Oct 2024 19:42:01 -0300 Subject: [PATCH] Improve pill for showing version & state --- lib/main.dart | 1 - lib/navigation/app_initializer.dart | 1 - lib/screens/app_detail_screen.dart | 464 +++++++++++++------------- lib/widgets/app_card.dart | 42 +-- lib/widgets/install_button.dart | 2 +- lib/widgets/spinning_logo.dart | 7 +- lib/widgets/version_pill_widget.dart | 73 ++++ lib/widgets/versioned_app_header.dart | 40 +-- 8 files changed, 328 insertions(+), 302 deletions(-) create mode 100644 lib/widgets/version_pill_widget.dart diff --git a/lib/main.dart b/lib/main.dart index 8c8fd86..f572797 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,7 +8,6 @@ import 'package:flutter_data/flutter_data.dart'; import 'package:flutter_phoenix/flutter_phoenix.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:zapstore/models/nostr_adapter.dart'; import 'package:zapstore/navigation/router.dart'; import 'package:zapstore/utils/debounce.dart'; import 'package:zapstore/utils/theme.dart'; diff --git a/lib/navigation/app_initializer.dart b/lib/navigation/app_initializer.dart index 53c1e4d..1f02d11 100644 --- a/lib/navigation/app_initializer.dart +++ b/lib/navigation/app_initializer.dart @@ -5,7 +5,6 @@ import 'package:zapstore/main.dart'; import 'package:zapstore/main.data.dart'; import 'package:zapstore/models/app.dart'; import 'package:zapstore/models/local_app.dart'; -import 'package:zapstore/models/nostr_adapter.dart'; import 'package:zapstore/navigation/router.dart'; import 'package:zapstore/widgets/app_curation_container.dart'; diff --git a/lib/screens/app_detail_screen.dart b/lib/screens/app_detail_screen.dart index f3aea9b..8666191 100644 --- a/lib/screens/app_detail_screen.dart +++ b/lib/screens/app_detail_screen.dart @@ -14,6 +14,7 @@ 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/spinning_logo.dart'; import 'package:zapstore/widgets/versioned_app_header.dart'; class AppDetailScreen extends HookConsumerWidget { @@ -44,260 +45,249 @@ class AppDetailScreen extends HookConsumerWidget { return RefreshIndicator( onRefresh: () => ref.apps.findOne(model.identifier, remote: true), - child: !snapshot.hasData - ? Center(child: CircularProgressIndicator()) - : Column( - children: [ - Expanded( - child: CustomScrollView( - slivers: [ - SliverList( - delegate: SliverChildListDelegate( - [ - VersionedAppHeader(app: app), - Gap(16), - if (app.images.isNotEmpty) - Scrollbar( - controller: scrollController, - interactive: true, - trackVisibility: true, - child: SingleChildScrollView( - controller: scrollController, - scrollDirection: Axis.horizontal, - child: SizedBox( - height: 320, - child: Row( - children: [ - for (final i in app.images) - Padding( - padding: const EdgeInsets.only( - right: 12), - child: CachedNetworkImage( - imageUrl: i, - errorWidget: (_, __, ___) => - Container(), - ), - ), - ], - ), - ), - ), - ), - Divider(height: 24), - MarkdownBody( - styleSheet: MarkdownStyleSheet( - h1: TextStyle(fontWeight: FontWeight.bold), - h2: TextStyle(fontWeight: FontWeight.bold), - p: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w300, - ), - ), - selectable: false, - data: app.content.parseEmojis(), - ), - Gap(10), - Padding( - padding: const EdgeInsets.only(right: 14), - child: SignerAndDeveloperRow(app: app), - ), - Gap(20), - if (app.repository == null) - Container( - padding: EdgeInsets.all(10), - decoration: BoxDecoration( - color: Colors.red[800], - borderRadius: BorderRadius.circular(10), - ), - child: Text( - '⚠️ Source code for this app is not available', - style: - TextStyle(fontWeight: FontWeight.bold)), - ), - Gap(10), - Container( - padding: EdgeInsets.all(10), - decoration: BoxDecoration( - color: Colors.grey[900], - borderRadius: BorderRadius.circular(10), - ), - child: Column( + child: Column( + children: [ + Expanded( + child: CustomScrollView( + slivers: [ + SliverList( + delegate: SliverChildListDelegate( + [ + VersionedAppHeader(app: app), + Gap(16), + if (app.images.isNotEmpty) + Scrollbar( + controller: scrollController, + interactive: true, + trackVisibility: true, + child: SingleChildScrollView( + controller: scrollController, + scrollDirection: Axis.horizontal, + child: SizedBox( + height: 320, + child: Row( children: [ - if (app.repository != null) + for (final i in app.images) Padding( - padding: const EdgeInsets.all(8), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text('Source'), - Gap(10), - Flexible( - child: GestureDetector( - onTap: () { - launchUrl( - Uri.parse(app.repository!)); - }, - child: AutoSizeText( - app.repository!, - minFontSize: 12, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.all(8), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text('License'), - Text((app.license == null || - app.license == 'NOASSERTION') - ? 'Unknown' - : app.license!) - ], - ), - ), - Padding( - padding: const EdgeInsets.all(8), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text('App ID'), - Gap(10), - Flexible( - child: AutoSizeText( - app.identifier, - minFontSize: 12, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ) - ], - ), - ), - if (app.latestMetadata != null) - Padding( - padding: const EdgeInsets.all(8), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text('APK package SHA-256'), - Flexible( - child: GestureDetector( - onTap: () { - Clipboard.setData(ClipboardData( - text: app.latestMetadata! - .hash!)); - context.showInfo( - 'Copied APK package SHA-256 to the clipboard'); - }, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - '${app.latestMetadata!.hash!.substring(0, 6)}...${app.latestMetadata!.hash!.substring(58, 64)}', - maxLines: 1, - ), - Gap(6), - Icon(Icons.copy_rounded, - size: 18) - ], - ), - ), - ), - ], - ), - ), - if (app.latestMetadata?.apkSignatureHash != - null) - Padding( - padding: const EdgeInsets.all(8), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text('APK certificate SHA-256'), - Flexible( - child: GestureDetector( - onTap: () { - Clipboard.setData(ClipboardData( - text: app.latestMetadata! - .apkSignatureHash!)); - context.showInfo( - 'Copied APK certificate SHA-256 to the clipboard'); - app - .packageCertificateMatches() - .then((match) { - if (match != null && !match) { - context.showError( - title: - 'APK certificate mismatch!', - description: - 'Please let us know', - ); - } - }); - }, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - '${app.latestMetadata!.apkSignatureHash!.substring(0, 6)}...${app.latestMetadata!.apkSignatureHash!.substring(58, 64)}', - maxLines: 1, - ), - Gap(6), - Icon(Icons.copy_rounded, - size: 18) - ], - ), - ), - ), - ], + padding: const EdgeInsets.only(right: 12), + child: CachedNetworkImage( + imageUrl: i, + errorWidget: (_, __, ___) => + Container(), ), ), ], ), ), - Divider(height: 60), - Text( - 'Latest release'.toUpperCase(), - style: TextStyle( - fontSize: 16, - letterSpacing: 3, - fontWeight: FontWeight.w300, + ), + ), + Divider(height: 24), + MarkdownBody( + styleSheet: MarkdownStyleSheet( + h1: TextStyle(fontWeight: FontWeight.bold), + h2: TextStyle(fontWeight: FontWeight.bold), + p: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w300, + ), + ), + selectable: false, + data: app.content.parseEmojis(), + ), + Gap(10), + Padding( + padding: const EdgeInsets.only(right: 14), + child: SignerAndDeveloperRow(app: app), + ), + Gap(20), + if (app.repository == null) + Container( + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.red[800], + borderRadius: BorderRadius.circular(10), + ), + child: Text( + '⚠️ Source code for this app is not available', + style: TextStyle(fontWeight: FontWeight.bold)), + ), + Gap(10), + Container( + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.grey[900], + borderRadius: BorderRadius.circular(10), + ), + child: Column( + children: [ + if (app.repository != null) + Padding( + padding: const EdgeInsets.all(8), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text('Source'), + Gap(10), + Flexible( + child: GestureDetector( + onTap: () { + launchUrl(Uri.parse(app.repository!)); + }, + child: AutoSizeText( + app.repository!, + minFontSize: 12, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text('License'), + Text((app.license == null || + app.license == 'NOASSERTION') + ? 'Unknown' + : app.license!) + ], ), ), - Gap(10), - if (app.releases.isEmpty) - Text('No available releases'), - if (app.releases.isNotEmpty) - ReleaseCard( - release: app.releases - .toList() - .sortedByLatest - .first), + Padding( + padding: const EdgeInsets.all(8), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text('App ID'), + Gap(10), + Flexible( + child: AutoSizeText( + app.identifier, + minFontSize: 12, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ) + ], + ), + ), + if (app.latestMetadata != null) + Padding( + padding: const EdgeInsets.all(8), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text('APK package SHA-256'), + Flexible( + child: GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData( + text: app.latestMetadata!.hash!)); + context.showInfo( + 'Copied APK package SHA-256 to the clipboard'); + }, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '${app.latestMetadata!.hash!.substring(0, 6)}...${app.latestMetadata!.hash!.substring(58, 64)}', + maxLines: 1, + ), + Gap(6), + Icon(Icons.copy_rounded, size: 18) + ], + ), + ), + ), + ], + ), + ), + if (app.latestMetadata?.apkSignatureHash != null) + Padding( + padding: const EdgeInsets.all(8), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text('APK certificate SHA-256'), + Flexible( + child: GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData( + text: app.latestMetadata! + .apkSignatureHash!)); + context.showInfo( + 'Copied APK certificate SHA-256 to the clipboard'); + app + .packageCertificateMatches() + .then((match) { + if (match != null && !match) { + context.showError( + title: + 'APK certificate mismatch!', + description: + 'Please let us know', + ); + } + }); + }, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '${app.latestMetadata!.apkSignatureHash!.substring(0, 6)}...${app.latestMetadata!.apkSignatureHash!.substring(58, 64)}', + maxLines: 1, + ), + Gap(6), + Icon(Icons.copy_rounded, size: 18) + ], + ), + ), + ), + ], + ), + ), ], ), ), + Divider(height: 60), + Text( + 'Latest release'.toUpperCase(), + style: TextStyle( + fontSize: 16, + letterSpacing: 3, + fontWeight: FontWeight.w300, + ), + ), + Gap(10), + if (app.releases.isEmpty) Text('No available releases'), + if (!snapshot.hasData) SpinningLogo(size: 120), + if (app.releases.isNotEmpty) + ReleaseCard( + release: + app.releases.toList().sortedByLatest.first), ], ), ), - SizedBox( - height: 50, - child: Center( - child: InstallButton(app: app), - ), - ), ], ), + ), + SizedBox( + height: 50, + child: Center( + child: InstallButton(app: app), + ), + ), + ], + ), ); } } diff --git a/lib/widgets/app_card.dart b/lib/widgets/app_card.dart index f05d885..6488ee5 100644 --- a/lib/widgets/app_card.dart +++ b/lib/widgets/app_card.dart @@ -7,12 +7,12 @@ 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/local_app.dart'; import 'package:zapstore/utils/extensions.dart'; import 'package:zapstore/widgets/author_container.dart'; import 'package:zapstore/widgets/install_button.dart'; import 'package:zapstore/widgets/pill_widget.dart'; import 'package:zapstore/widgets/rounded_image.dart'; +import 'package:zapstore/widgets/version_pill_widget.dart'; class AppCard extends HookConsumerWidget { final App model; @@ -68,45 +68,7 @@ class AppCard extends HookConsumerWidget { maxLines: 1, ), ), - if (app.latestMetadata?.version != null) - if (isUpdate) - PillWidget( - text: TextSpan( - text: app.localApp.value!.installedVersion!), - color: Colors.grey[800]!, - size: 11, - ), - if (!isUpdate) - PillWidget( - text: WidgetSpan( - alignment: PlaceholderAlignment.middle, - child: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - Text( - app.latestMetadata!.version!, - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 11, - ), - ), - if (app.localApp.value?.status == - AppInstallStatus.updatable) - Row( - children: [ - Gap(5), - Icon(Icons.update_outlined, size: 15), - ], - ), - ], - ), - ), - size: 10, - color: app.localApp.value?.status == - AppInstallStatus.updatable - ? kUpdateColor - : Colors.grey[800]!, - ), + VersionPillWidget(app: app), ], ), Gap(6), diff --git a/lib/widgets/install_button.dart b/lib/widgets/install_button.dart index 44854a6..d8c6d06 100644 --- a/lib/widgets/install_button.dart +++ b/lib/widgets/install_button.dart @@ -89,7 +89,7 @@ class InstallButton extends ConsumerWidget { ? Padding( padding: const EdgeInsets.only(left: 8, right: 8), child: AutoSizeText( - 'Update to ${app.latestMetadata!.version!}', + 'Update', maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( diff --git a/lib/widgets/spinning_logo.dart b/lib/widgets/spinning_logo.dart index 0fab749..860de33 100644 --- a/lib/widgets/spinning_logo.dart +++ b/lib/widgets/spinning_logo.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; class SpinningLogo extends StatefulWidget { - const SpinningLogo({super.key}); + final double size; + const SpinningLogo({super.key, this.size = 200}); @override SpinningLogoState createState() => SpinningLogoState(); @@ -40,8 +41,8 @@ class SpinningLogoState extends State }, child: Image.asset( 'assets/images/logo-fg.png', - height: 200, - width: 200, + height: widget.size, + width: widget.size, ), // Adjust size as needed ), ); diff --git a/lib/widgets/version_pill_widget.dart b/lib/widgets/version_pill_widget.dart new file mode 100644 index 0000000..ddf8c75 --- /dev/null +++ b/lib/widgets/version_pill_widget.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:zapstore/models/app.dart'; +import 'package:zapstore/widgets/install_button.dart'; +import 'package:zapstore/widgets/pill_widget.dart'; + +class VersionPillWidget extends StatelessWidget { + const VersionPillWidget({ + super.key, + required this.app, + }); + + final App app; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + if (app.canUpdate) + PillWidget( + text: WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + Text( + app.localApp.value!.installedVersion!, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 11, + ), + ), + ], + ), + ), + size: 10, + color: Colors.grey[800]!, + ), + if (app.canUpdate) Icon(Icons.arrow_right), + PillWidget( + text: WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + if (app.latestMetadata != null) + Text( + app.latestMetadata!.version!, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 11, + ), + ), + Row( + children: [ + Gap(5), + if (app.canUpdate) Icon(Icons.update_outlined, size: 15), + if (app.canInstall) Icon(Icons.download_rounded, size: 15), + if (app.isUpdated) Icon(Icons.check, size: 15), + ], + ), + ], + ), + ), + size: 10, + color: app.canUpdate + ? kUpdateColor + : (app.canInstall ? Colors.grey[800]! : kUpdateColor), + ), + ], + ); + } +} diff --git a/lib/widgets/versioned_app_header.dart b/lib/widgets/versioned_app_header.dart index dfcdf7a..2ab5391 100644 --- a/lib/widgets/versioned_app_header.dart +++ b/lib/widgets/versioned_app_header.dart @@ -3,8 +3,8 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:zapstore/models/app.dart'; -import 'package:zapstore/widgets/pill_widget.dart'; import 'package:zapstore/widgets/rounded_image.dart'; +import 'package:zapstore/widgets/version_pill_widget.dart'; class VersionedAppHeader extends StatelessWidget { const VersionedAppHeader({ @@ -40,24 +40,26 @@ class VersionedAppHeader extends StatelessWidget { overflow: TextOverflow.ellipsis, ), Gap(10), - Row( - children: [ - if (app.localApp.value?.installedVersion != null) - PillWidget( - text: - TextSpan(text: app.localApp.value!.installedVersion!), - color: Colors.grey[800]!, - size: 11, - ), - Icon(Icons.arrow_right), - if (app.canUpdate) - PillWidget( - text: TextSpan(text: app.latestMetadata!.version!), - color: Colors.blue[700]!, - size: 11, - ), - ], - ), + VersionPillWidget(app: app), + // Row( + // children: [ + // if (app.localApp.value?.installedVersion != null) + // PillWidget( + // text: + // TextSpan(text: app.localApp.value!.installedVersion!), + // color: Colors.grey[800]!, + // size: 11, + // ), + // Icon(Icons.arrow_right), + // if (app.canUpdate) + // PillWidget( + // text: TextSpan(text: app.latestMetadata!.version!), + // color: Colors.blue[700]!, + // size: 11, + // ), + // if (app.canInstall) , + // ], + // ), ], ), ),