diff --git a/lib/screens/app_detail_screen.dart b/lib/screens/app_detail_screen.dart index c6b7c21..c98382b 100644 --- a/lib/screens/app_detail_screen.dart +++ b/lib/screens/app_detail_screen.dart @@ -10,7 +10,6 @@ import 'package:zapstore/main.data.dart'; import 'package:zapstore/models/app.dart'; import 'package:zapstore/models/release.dart'; import 'package:zapstore/utils/extensions.dart'; -import 'package:zapstore/widgets/app_card.dart'; import 'package:zapstore/widgets/install_button.dart'; import 'package:zapstore/widgets/release_card.dart'; import 'package:zapstore/widgets/signer_and_developer_row.dart'; @@ -221,7 +220,11 @@ class AppDetailScreen extends HookConsumerWidget { .then((match) { if (match != null && !match) { context.showError( - 'APK certificate mismatch!\nPlease report'); + title: + 'APK certificate mismatch!', + description: + 'Please let us know', + ); } }); }, @@ -268,7 +271,7 @@ class AppDetailScreen extends HookConsumerWidget { SizedBox( height: 50, child: Center( - child: InstallButton(app: app, disabled: !app.signer.isPresent), + child: InstallButton(app: app), ), ), ], diff --git a/lib/utils/extensions.dart b/lib/utils/extensions.dart index c84a4cb..986298b 100644 --- a/lib/utils/extensions.dart +++ b/lib/utils/extensions.dart @@ -1,3 +1,6 @@ +import 'dart:math'; + +import 'package:dart_emoji/dart_emoji.dart'; import 'package:flutter/material.dart'; import 'package:flutter_data/flutter_data.dart'; import 'package:toastification/toastification.dart'; @@ -21,18 +24,19 @@ extension ContextX on BuildContext { ); } - void showError(String message, {Icon? icon}) { + void showError({required String title, String? description, Icon? icon}) { toastification.show( context: this, type: ToastificationType.error, style: ToastificationStyle.fillColored, icon: icon ?? Icon(Icons.error), title: Text( - message, + title, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), showProgressBar: false, closeOnClick: true, + description: description != null ? Text(description) : null, alignment: Alignment.lerp(Alignment.bottomCenter, Alignment.center, 0.25), ); } @@ -46,4 +50,17 @@ HasMany hasMany>(Map map) { return HasMany.fromJson(map); } +extension StringWidget on String { + static final _emojiParser = EmojiParser(); + String parseEmojis() { + return replaceAllMapped(RegExp(':([a-z]*):'), (m) { + return _emojiParser.hasName(m[1]!) ? _emojiParser.get(m[1]!).code : m[0]!; + }); + } + + String substringMin(int size) { + return substring(0, min(length, 80)); + } +} + const kI = "e593c54f840b32054dcad0fac15d57e4ac6523e31fe26b3087de6b07a2e9af58"; diff --git a/lib/widgets/app_card.dart b/lib/widgets/app_card.dart index 3b21da7..f05d885 100644 --- a/lib/widgets/app_card.dart +++ b/lib/widgets/app_card.dart @@ -1,5 +1,4 @@ import 'package:auto_size_text/auto_size_text.dart'; -import 'package:dart_emoji/dart_emoji.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; @@ -9,6 +8,7 @@ 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'; @@ -267,12 +267,3 @@ class TagsContainer extends StatelessWidget { ); } } - -extension StringWidget on String { - static final _emojiParser = EmojiParser(); - String parseEmojis() { - return replaceAllMapped(RegExp(':([a-z]*):'), (m) { - return _emojiParser.hasName(m[1]!) ? _emojiParser.get(m[1]!).code : m[0]!; - }); - } -} diff --git a/lib/widgets/app_drawer.dart b/lib/widgets/app_drawer.dart index 3dd57d8..6d3271e 100644 --- a/lib/widgets/app_drawer.dart +++ b/lib/widgets/app_drawer.dart @@ -81,8 +81,10 @@ class LoginContainer extends HookConsumerWidget { .findOne(controller.text.trim()) .then((user) { ref.settings.findOneLocalById('_')!.user.value = user; - }).catchError((e) { - context.showError(e.message ?? e.toString()); + }).catchError((e, stack) { + context.showError( + title: e.message ?? e.toString(), + description: stack?.toString().substringMin(200)); }); }, builder: (context, child, callback, buttonState) { diff --git a/lib/widgets/error_container.dart b/lib/widgets/error_container.dart index a56d712..73b59b0 100644 --- a/lib/widgets/error_container.dart +++ b/lib/widgets/error_container.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_data/flutter_data.dart'; @@ -49,7 +50,7 @@ class ErrorContainer extends HookConsumerWidget { ), Gap(6), Text( - '${stack?.toString().substring(0, 80)}', + stack?.toString().substringMin(80) ?? '', style: TextStyle( fontSize: 12, color: Colors.grey, diff --git a/lib/widgets/horizontal_grid.dart b/lib/widgets/horizontal_grid.dart new file mode 100644 index 0000000..4a06311 --- /dev/null +++ b/lib/widgets/horizontal_grid.dart @@ -0,0 +1,28 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:zapstore/models/app.dart'; +import 'package:zapstore/widgets/app_card.dart'; + +class HorizontalGrid extends StatelessWidget { + final List apps; + + HorizontalGrid({super.key, required this.apps}); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 230, + child: GridView.builder( + scrollDirection: Axis.horizontal, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + childAspectRatio: 1.15, + crossAxisSpacing: 8, + mainAxisSpacing: 8, + ), + itemCount: apps.isEmpty ? 8 : apps.length, + itemBuilder: (context, i) => TinyAppCard(app: apps.elementAtOrNull(i)), + ), + ); + } +} diff --git a/lib/widgets/install_button.dart b/lib/widgets/install_button.dart index fc7cff6..44854a6 100644 --- a/lib/widgets/install_button.dart +++ b/lib/widgets/install_button.dart @@ -18,10 +18,8 @@ class InstallButton extends ConsumerWidget { super.key, required this.app, this.compact = false, - this.disabled = false, }); - final bool disabled; final bool compact; final App app; @@ -38,10 +36,6 @@ class InstallButton extends ConsumerWidget { }, _ => switch (progress) { IdleInstallProgress() => () { - // show trust dialog only if first install - if (disabled) { - context.showError('Missing signer'); - } if (app.canInstall) { showDialog( context: context, @@ -51,11 +45,15 @@ class InstallButton extends ConsumerWidget { ); } else if (app.canUpdate) { app.install(); + } else { + context.showError( + title: 'Can\'t install', + description: 'Release or signer are missing.'); } }, ErrorInstallProgress(:final e) => () { // show error and reset state to idle - context.showError((e as dynamic).message); + context.showError(title: (e as dynamic).message); ref.read(installationProgressProvider(app.id!).notifier).state = IdleInstallProgress(); }, @@ -99,7 +97,9 @@ class InstallButton extends ConsumerWidget { fontWeight: FontWeight.bold), ), ) - : Text('Install'), + : (app.canInstall + ? Text('Install') + : Text('Nothing to install')), DownloadingInstallProgress(:final progress) => Text( '${(progress * 100).floor()}%', style: TextStyle(fontWeight: FontWeight.bold),