diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 0706a26f4..3dc09fc19 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -49,5 +49,6 @@ "size": "Size", "name": "Name", "sortBy": "Sort by", - "media": "Media" + "media": "Media", + "done": "Done" } \ No newline at end of file diff --git a/lib/services/app_change_service.dart b/lib/services/app_change_service.dart index adfa53245..e3f9cffba 100644 --- a/lib/services/app_change_service.dart +++ b/lib/services/app_change_service.dart @@ -10,7 +10,7 @@ class AppChangeService { final SnapdClient _snapDClient; final NotificationsClient _notificationsClient; - Future addChange(Snap snap, String id) async { + Future addChange(Snap snap, String id, String doneString) async { final newChange = await _snapDClient.getChange(id); _snapChanges.putIfAbsent(snap, () => newChange); if (!_snapChangesController.isClosed) { @@ -22,12 +22,12 @@ class AppChangeService { removeChange(snap); _notificationsClient.notify( 'Software', - body: newChange.summary, + body: '$doneString: ${newChange.summary}', appName: snap.name, appIcon: 'snap-store', hints: [ NotificationHint.desktopEntry('software'), - NotificationHint.urgency(NotificationUrgency.critical) + NotificationHint.urgency(NotificationUrgency.normal) ], ); break; diff --git a/lib/store_app/common/snap_dialog.dart b/lib/store_app/common/snap_dialog.dart index c97b05869..799eff609 100644 --- a/lib/store_app/common/snap_dialog.dart +++ b/lib/store_app/common/snap_dialog.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:snapd/snapd.dart'; +import 'package:software/l10n/l10n.dart'; import 'package:software/services/app_change_service.dart'; import 'package:software/store_app/common/constants.dart'; import 'package:software/store_app/common/snap_channel_expandable.dart'; @@ -23,7 +24,8 @@ class SnapDialog extends StatefulWidget { required String huskSnapName, }) => ChangeNotifierProvider( - create: (context) => SnapModel( + create: (_) => SnapModel( + doneString: context.l10n.done, getService(), getService(), huskSnapName: huskSnapName, diff --git a/lib/store_app/common/snap_model.dart b/lib/store_app/common/snap_model.dart index f48a8198e..b70c79b99 100644 --- a/lib/store_app/common/snap_model.dart +++ b/lib/store_app/common/snap_model.dart @@ -1,17 +1,13 @@ import 'dart:async'; -import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:intl/intl.dart'; import 'package:safe_change_notifier/safe_change_notifier.dart'; import 'package:snapd/snapd.dart'; import 'package:software/services/app_change_service.dart'; import 'package:software/services/color_generator.dart'; import 'package:software/snapx.dart'; -import 'package:xdg_icons/xdg_icons.dart'; -import 'package:yaru_icons/yaru_icons.dart'; class SnapModel extends SafeChangeNotifier { final AppChangeService _appChangeService; @@ -21,10 +17,12 @@ class SnapModel extends SafeChangeNotifier { Snap? _storeSnap; Snap? _localSnap; bool online; + final String doneString; SnapModel( this._client, this._appChangeService, { + required this.doneString, this.colorGenerator, required this.huskSnapName, this.online = true, @@ -245,6 +243,7 @@ class SnapModel extends SafeChangeNotifier { await _appChangeService.addChange( _storeSnap!, changeId, + doneString, ); _localSnap = await _findLocalSnap(huskSnapName); notifyListeners(); @@ -254,7 +253,11 @@ class SnapModel extends SafeChangeNotifier { if (name == null) return; await _client.loadAuthorization(); final changeId = await _client.remove(name!); - await _appChangeService.addChange(_localSnap!, changeId); + await _appChangeService.addChange( + _localSnap!, + changeId, + doneString, + ); _localSnap = await _findLocalSnap(huskSnapName); notifyListeners(); } @@ -264,7 +267,11 @@ class SnapModel extends SafeChangeNotifier { await _client.loadAuthorization(); final changeId = await _client.refresh(name!, channel: channelToBeInstalled); - await _appChangeService.addChange(_localSnap!, changeId); + await _appChangeService.addChange( + _localSnap!, + changeId, + doneString, + ); notifyListeners(); } @@ -342,62 +349,4 @@ class SnapModel extends SafeChangeNotifier { mode: ProcessStartMode.detached, ); } - - String? get _desktopFile => - apps != null && apps!.isNotEmpty && apps!.first.desktopFile != null - ? apps!.first.desktopFile! - : null; - - Widget offlineIcon = fallbackSnapIcon; - String _iconLine = ''; - - void loadOfflineIcon() { - if (_desktopFile != null) { - File? file = File(_desktopFile!); - (file - .openRead() - .map(utf8.decode) - .transform(const LineSplitter()) - .where((line) => line.contains('Icon=')) - .first) - .then((line) { - _iconLine = line.replaceAll('Icon=', ''); - if (_iconLine.endsWith('.png') || - _iconLine.endsWith('.jpg') || - _iconLine.endsWith('.jpeg')) { - offlineIcon = Image.file( - File(_iconLine), - filterQuality: FilterQuality.medium, - width: 50, - ); - } - if (_iconLine.endsWith('.svg')) { - try { - offlineIcon = SvgPicture.file( - File(_iconLine), - width: 50, - ); - } finally { - if (offlineIcon != fallbackSnapIcon) { - offlineIcon = fallbackSnapIcon; - } - } - } - if (!_iconLine.contains('/')) { - offlineIcon = XdgIconTheme( - data: const XdgIconThemeData(theme: 'Yaru'), - child: XdgIcon(name: _iconLine, size: 48), - ); - } - notifyListeners(); - return; - }); - } - notifyListeners(); - } } - -const fallbackSnapIcon = Icon( - YaruIcons.package_snap, - size: 50, -); diff --git a/lib/store_app/explore/section_banner_grid.dart b/lib/store_app/explore/section_banner_grid.dart index 67acc3cf9..d0db9480e 100644 --- a/lib/store_app/explore/section_banner_grid.dart +++ b/lib/store_app/explore/section_banner_grid.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:software/snapx.dart'; +import 'package:software/store_app/common/snap_dialog.dart'; import 'package:software/store_app/common/snap_section.dart'; import 'package:software/store_app/explore/explore_model.dart'; import 'package:software/store_app/explore/snap_banner.dart'; @@ -43,7 +45,18 @@ class _SectionBannerGridState extends State { ), children: sections != null && sections.isNotEmpty ? sections.take(widget.amount).map((snap) { - return SnapBanner.create(context, snap); + return SnapBanner( + name: snap.name, + summary: snap.summary, + url: snap.iconUrl, + onTap: () => showDialog( + context: context, + builder: (context) => SnapDialog.create( + context: context, + huskSnapName: snap.name, + ), + ), + ); }).toList() : [], ); diff --git a/lib/store_app/explore/snap_banner.dart b/lib/store_app/explore/snap_banner.dart index f8392c166..31f2592d5 100644 --- a/lib/store_app/explore/snap_banner.dart +++ b/lib/store_app/explore/snap_banner.dart @@ -1,52 +1,27 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:snapd/snapd.dart'; -import 'package:software/services/app_change_service.dart'; -import 'package:software/snapx.dart'; -import 'package:software/store_app/common/safe_image.dart'; -import 'package:software/store_app/common/snap_dialog.dart'; import 'package:software/store_app/common/app_banner.dart'; - -import 'package:software/store_app/common/snap_model.dart'; -import 'package:ubuntu_service/ubuntu_service.dart'; +import 'package:software/store_app/common/safe_image.dart'; import 'package:yaru_colors/yaru_colors.dart'; import 'package:yaru_icons/yaru_icons.dart'; class SnapBanner extends StatelessWidget { const SnapBanner({ Key? key, - required this.snap, this.onTap, this.surfaceTintColor, this.watermark = false, + required this.name, + required this.summary, + this.url, }) : super(key: key); - final Snap snap; + final String name; + final String summary; + final String? url; final Function()? onTap; final Color? surfaceTintColor; final bool watermark; - static Widget create(BuildContext context, Snap snap) { - final snapModel = SnapModel( - getService(), - getService(), - huskSnapName: snap.name, - ); - return ChangeNotifierProvider( - create: (context) => snapModel, - child: SnapBanner( - snap: snap, - onTap: () => showDialog( - context: context, - builder: (context) => ChangeNotifierProvider.value( - value: snapModel, - child: const SnapDialog(), - ), - ), - ), - ); - } - @override Widget build(BuildContext context) { final borderRadius = BorderRadius.circular(10); @@ -61,11 +36,11 @@ class SnapBanner extends StatelessWidget { AppBanner( borderRadius: borderRadius, color: surfaceTintColor!, - title: snap.title ?? '', - summary: snap.summary, + title: name, + summary: summary, elevation: light ? 4 : 6, icon: SafeImage( - url: snap.iconUrl, + url: url, fallBackIconData: YaruIcons.package_snap, ), textOverflow: TextOverflow.fade, @@ -80,7 +55,7 @@ class SnapBanner extends StatelessWidget { child: SizedBox( height: 130, child: SafeImage( - url: snap.iconUrl, + url: url, fallBackIconData: YaruIcons.package_snap, ), ), @@ -96,11 +71,11 @@ class SnapBanner extends StatelessWidget { : Theme.of(context).colorScheme.onBackground, elevation: light ? 2 : 1, icon: SafeImage( - url: snap.iconUrl, + url: url, fallBackIconData: YaruIcons.package_snap, ), - title: snap.title ?? '', - summary: snap.summary, + title: name, + summary: summary, textOverflow: TextOverflow.ellipsis, ), ); diff --git a/lib/store_app/explore/snap_banner_carousel.dart b/lib/store_app/explore/snap_banner_carousel.dart index 20c7aac79..39a220720 100644 --- a/lib/store_app/explore/snap_banner_carousel.dart +++ b/lib/store_app/explore/snap_banner_carousel.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:snapd/snapd.dart'; +import 'package:software/l10n/l10n.dart'; import 'package:software/services/app_change_service.dart'; +import 'package:software/snapx.dart'; import 'package:software/store_app/common/snap_model.dart'; import 'package:software/store_app/common/snap_section.dart'; import 'package:software/store_app/explore/explore_model.dart'; @@ -77,6 +79,7 @@ class _AppBannerCarouselItem extends StatefulWidget { getService(), huskSnapName: snap.name, colorGenerator: getService(), + doneString: context.l10n.done, ), child: _AppBannerCarouselItem(snap: snap), ); @@ -98,12 +101,16 @@ class _AppBannerCarouselItemState extends State<_AppBannerCarouselItem> { final model = context.watch(); return SnapBanner( watermark: true, - snap: widget.snap, + name: widget.snap.name, + summary: widget.snap.summary, + url: widget.snap.iconUrl, surfaceTintColor: model.surfaceTintColor, onTap: () => showDialog( context: context, - builder: (context) => - SnapDialog.create(context: context, huskSnapName: widget.snap.name), + builder: (context) => ChangeNotifierProvider.value( + value: model, + child: const SnapDialog(), + ), ), ); } diff --git a/lib/store_app/my_apps/local_snap_banner.dart b/lib/store_app/my_apps/local_snap_banner.dart index 951706aaf..71dffee16 100644 --- a/lib/store_app/my_apps/local_snap_banner.dart +++ b/lib/store_app/my_apps/local_snap_banner.dart @@ -1,57 +1,31 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:snapd/snapd.dart'; -import 'package:software/services/app_change_service.dart'; import 'package:software/store_app/common/app_banner.dart'; +import 'package:software/store_app/common/safe_image.dart'; import 'package:software/store_app/common/snap_dialog.dart'; - -import 'package:software/store_app/common/snap_model.dart'; -import 'package:ubuntu_service/ubuntu_service.dart'; import 'package:yaru_colors/yaru_colors.dart'; +import 'package:yaru_icons/yaru_icons.dart'; -class LocalSnapBanner extends StatefulWidget { - const LocalSnapBanner({Key? key, required this.online}) : super(key: key); - - final bool online; - - static Widget create(BuildContext context, String snapName, bool online) { - final snapModel = SnapModel( - getService(), - getService(), - huskSnapName: snapName, - online: online, - ); - return ChangeNotifierProvider( - create: (context) => snapModel, - child: LocalSnapBanner(online: online), - ); - } - - @override - State createState() => _LocalSnapBannerState(); -} +class LocalSnapBanner extends StatelessWidget { + final String snapName; + final String summary; + final String? url; -class _LocalSnapBannerState extends State { - @override - void initState() { - final model = context.read(); - model.init().then((value) => model.loadOfflineIcon()); - - super.initState(); - } + const LocalSnapBanner({ + Key? key, + required this.snapName, + required this.summary, + required this.url, + }) : super(key: key); @override Widget build(BuildContext context) { final borderRadius = BorderRadius.circular(10); bool light = Theme.of(context).brightness == Brightness.light; - final model = context.watch(); return InkWell( onTap: () => showDialog( context: context, - builder: (context) => ChangeNotifierProvider.value( - value: model, - child: const SnapDialog(), - ), + builder: (context) => + SnapDialog.create(context: context, huskSnapName: snapName), ), borderRadius: borderRadius, child: AppBanner( @@ -60,9 +34,12 @@ class _LocalSnapBannerState extends State { ? YaruColors.warmGrey.shade900 : Theme.of(context).colorScheme.onBackground, elevation: light ? 2 : 1, - icon: model.offlineIcon, - title: model.title ?? '______________', - summary: model.summary ?? '______________', + icon: SafeImage( + url: url, + fallBackIconData: YaruIcons.package_snap, + ), + title: snapName, + summary: summary, textOverflow: TextOverflow.ellipsis, ), ); diff --git a/lib/store_app/my_apps/my_snaps_model.dart b/lib/store_app/my_apps/my_snaps_model.dart index 948503ce8..b262cda31 100644 --- a/lib/store_app/my_apps/my_snaps_model.dart +++ b/lib/store_app/my_apps/my_snaps_model.dart @@ -8,7 +8,7 @@ class MySnapsModel extends SafeChangeNotifier { final SnapdClient _client; final AppChangeService _appChangeService; StreamSubscription? _snapChangesSub; - List _localSnaps; + final List _localSnaps; List get localSnaps => _localSnaps; MySnapsModel( @@ -18,9 +18,10 @@ class MySnapsModel extends SafeChangeNotifier { Future init() async { await _loadLocalSnaps(); - _snapChangesSub = _appChangeService.snapChangesInserted.listen((_) async { - await _loadLocalSnaps(); - notifyListeners(); + _snapChangesSub = _appChangeService.snapChangesInserted.listen((_) { + if (_appChangeService.snapChanges.isEmpty) { + _loadLocalSnaps().then((value) => notifyListeners()); + } }); notifyListeners(); } @@ -33,11 +34,13 @@ class MySnapsModel extends SafeChangeNotifier { Future _loadLocalSnaps() async { await _client.loadAuthorization(); - _localSnaps = (await _client.getSnaps()) + final snaps = (await _client.getSnaps()) .where( (snap) => _appChangeService.getChange(snap) == null, ) .toList(); - _localSnaps.sort((a, b) => a.name.compareTo(b.name)); + snaps.sort((a, b) => a.name.compareTo(b.name)); + _localSnaps.clear(); + _localSnaps.addAll(snaps); } } diff --git a/lib/store_app/my_apps/my_snaps_page.dart b/lib/store_app/my_apps/my_snaps_page.dart index 087f1c34f..5fa838bb1 100644 --- a/lib/store_app/my_apps/my_snaps_page.dart +++ b/lib/store_app/my_apps/my_snaps_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:snapd/snapd.dart'; import 'package:software/services/app_change_service.dart'; +import 'package:software/snapx.dart'; import 'package:software/store_app/my_apps/local_snap_banner.dart'; import 'package:software/store_app/my_apps/my_apps_page.dart'; import 'package:software/store_app/my_apps/my_snaps_model.dart'; @@ -38,20 +39,37 @@ class _MySnapsPageState extends State { Widget build(BuildContext context) { final mySnapsModel = context.watch(); return mySnapsModel.localSnaps.isNotEmpty - ? GridView.builder( - padding: const EdgeInsets.all(20.0), - gridDelegate: myAppsGridDelegate, - shrinkWrap: true, - itemCount: mySnapsModel.localSnaps.length, - itemBuilder: (context, index) { - final snap = mySnapsModel.localSnaps.elementAt(index); - return LocalSnapBanner.create( - context, - snap.name, - widget.online, - ); - }, - ) + ? _MySnapsGrid(snaps: mySnapsModel.localSnaps) : const SizedBox(); } } + +class _MySnapsGrid extends StatefulWidget { + // ignore: unused_element + const _MySnapsGrid({super.key, required this.snaps}); + + final List snaps; + + @override + State<_MySnapsGrid> createState() => __MySnapsGridState(); +} + +class __MySnapsGridState extends State<_MySnapsGrid> { + @override + Widget build(BuildContext context) { + return GridView.builder( + padding: const EdgeInsets.all(20.0), + gridDelegate: myAppsGridDelegate, + shrinkWrap: true, + itemCount: widget.snaps.length, + itemBuilder: (context, index) { + final snap = widget.snaps.elementAt(index); + return LocalSnapBanner( + snapName: snap.name, + summary: snap.summary, + url: snap.iconUrl, + ); + }, + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 40e8d16f7..1ec023626 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ description: An Ubuntu software app made with Flutter. publish_to: "none" -version: 0.0.5-alpha +version: 0.0.6-alpha environment: sdk: ">=2.17.0 <3.0.0"