Improve pill for showing version & state

This commit is contained in:
franzap
2024-10-22 19:42:01 -03:00
parent 2f15600d4e
commit f4c4ef2475
8 changed files with 328 additions and 302 deletions
-1
View File
@@ -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';
-1
View File
@@ -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';
+227 -237
View File
@@ -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),
),
),
],
),
);
}
}
+2 -40
View File
@@ -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),
+1 -1
View File
@@ -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(
+4 -3
View File
@@ -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<SpinningLogo>
},
child: Image.asset(
'assets/images/logo-fg.png',
height: 200,
width: 200,
height: widget.size,
width: widget.size,
), // Adjust size as needed
),
);
+73
View File
@@ -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),
),
],
);
}
}
+21 -19
View File
@@ -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) ,
// ],
// ),
],
),
),