diff --git a/melos.yaml b/melos.yaml index b4eda8a5d..177078115 100644 --- a/melos.yaml +++ b/melos.yaml @@ -11,7 +11,7 @@ command: flutter: '>=3.24.3' dev_dependencies: - ubuntu_lints: ^0.4.0 + ubuntu_lints: ^0.4.1 scripts: # build all packages diff --git a/packages/app_center/integration_test/app_center_test.dart b/packages/app_center/integration_test/app_center_test.dart index 7d93b1afe..0a46b1d4f 100644 --- a/packages/app_center/integration_test/app_center_test.dart +++ b/packages/app_center/integration_test/app_center_test.dart @@ -8,6 +8,7 @@ import 'package:integration_test/integration_test.dart'; import 'package:ubuntu_service/ubuntu_service.dart'; import 'package:ubuntu_test/ubuntu_test.dart'; import 'package:yaru/icons.dart'; +import 'package:yaru/widgets.dart'; import 'package:yaru_test/yaru_test.dart'; import '../test/test_utils.dart'; @@ -105,10 +106,14 @@ Future testRemoveSnap( }) async { final installButton = find.button(tester.l10n.snapActionInstallLabel); expect(installButton, findsNothing); + final menuButton = find.descendant( + of: find.byType(YaruSplitButton), + matching: find.iconButton(YaruIcons.pan_down), + ); - await tester.tap(find.iconButton(YaruIcons.view_more_horizontal)); + await tester.tap(menuButton); await tester.pumpAndSettle(); - await tester.tap(find.button(tester.l10n.snapActionRemoveLabel)); + await tester.tap(find.text(tester.l10n.snapActionRemoveLabel)); await tester.pumpUntil(installButton); expect(installedFile.existsSync(), isFalse); diff --git a/packages/app_center/lib/constants.dart b/packages/app_center/lib/constants.dart index db73fc941..cea54b241 100644 --- a/packages/app_center/lib/constants.dart +++ b/packages/app_center/lib/constants.dart @@ -11,7 +11,8 @@ const kShimmerBaseDark = Color.fromARGB(255, 51, 51, 51); const kShimmerHighLightLight = Color.fromARGB(200, 247, 247, 247); const kShimmerHighLightDark = Color.fromARGB(255, 57, 57, 57); -const kCircularProgressIndicatorHeight = 16.0; +const kLoaderHeight = 16.0; +const kLoaderMediumHeight = 32.0; const kSearchFieldIconConstraints = BoxConstraints( minWidth: 32, minHeight: 32, diff --git a/packages/app_center/lib/deb/deb_page.dart b/packages/app_center/lib/deb/deb_page.dart index d5d853caf..d39683cfc 100644 --- a/packages/app_center/lib/deb/deb_page.dart +++ b/packages/app_center/lib/deb/deb_page.dart @@ -177,7 +177,7 @@ class _DebActionButtons extends ConsumerWidget { .valueOrNull; return Center( child: SizedBox.square( - dimension: kCircularProgressIndicatorHeight, + dimension: kLoaderHeight, child: YaruCircularProgressIndicator( value: (transaction?.percentage ?? 0) / 100.0, strokeWidth: 2, diff --git a/packages/app_center/lib/deb/local_deb_page.dart b/packages/app_center/lib/deb/local_deb_page.dart index 678bf6130..f2414ea28 100644 --- a/packages/app_center/lib/deb/local_deb_page.dart +++ b/packages/app_center/lib/deb/local_deb_page.dart @@ -158,7 +158,7 @@ class _LocalDebActionButtons extends ConsumerWidget { ? Row( children: [ const SizedBox.square( - dimension: kCircularProgressIndicatorHeight, + dimension: kLoaderHeight, child: YaruCircularProgressIndicator(strokeWidth: 2), ), const SizedBox(width: 8), diff --git a/packages/app_center/lib/manage/manage_page.dart b/packages/app_center/lib/manage/manage_page.dart index 0b146204a..53cf882f6 100644 --- a/packages/app_center/lib/manage/manage_page.dart +++ b/packages/app_center/lib/manage/manage_page.dart @@ -125,7 +125,6 @@ class ManagePage extends ConsumerWidget { index: index, length: snapListState.snaps.length, ), - showUpdateButton: true, ), ); }, @@ -461,7 +460,7 @@ class _SmallLoadingIndicator extends StatelessWidget { @override Widget build(BuildContext context) { return SizedBox.square( - dimension: kCircularProgressIndicatorHeight, + dimension: kLoaderHeight, child: YaruCircularProgressIndicator( value: progress, strokeWidth: 2, diff --git a/packages/app_center/lib/manage/manage_snap_tile.dart b/packages/app_center/lib/manage/manage_snap_tile.dart index 2ff06bc77..dc41c6fd4 100644 --- a/packages/app_center/lib/manage/manage_snap_tile.dart +++ b/packages/app_center/lib/manage/manage_snap_tile.dart @@ -1,16 +1,12 @@ import 'package:app_center/l10n.dart'; import 'package:app_center/layout.dart'; import 'package:app_center/manage/manage_l10n.dart'; -import 'package:app_center/manage/update_button.dart'; -import 'package:app_center/snapd/snap_action.dart'; +import 'package:app_center/manage/snap_actions_button.dart'; import 'package:app_center/snapd/snapd.dart'; import 'package:app_center/store/store.dart'; -import 'package:app_center/widgets/snap_menu_item.dart'; import 'package:app_center/widgets/widgets.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:snapd/snapd.dart'; -import 'package:yaru/yaru.dart'; enum ManageTilePosition { first, middle, last, single } @@ -18,14 +14,12 @@ class ManageSnapTile extends StatelessWidget { const ManageSnapTile({ required this.snap, this.position = ManageTilePosition.middle, - this.showUpdateButton = false, this.hasFixedSize = false, super.key, }); final Snap snap; final ManageTilePosition position; - final bool showUpdateButton; final bool hasFixedSize; @override @@ -36,9 +30,9 @@ class ManageSnapTile extends StatelessWidget { ? DateTime.now().difference(snap.installDate!) : null; const radius = Radius.circular(8); - final buttonBar = Align( + final actionButtons = Align( alignment: Alignment.centerRight, - child: _ButtonBar(snap, showUpdateButton), + child: SnapActionButtons(snapName: snap.name, isPrimary: false), ); return DecoratedBox( @@ -169,8 +163,8 @@ class ManageSnapTile extends StatelessWidget { ], ), trailing: hasFixedSize - ? SizedBox(width: 180, child: buttonBar) - : IntrinsicWidth(child: buttonBar), + ? SizedBox(width: 200, child: actionButtons) + : IntrinsicWidth(child: actionButtons), ), ); } @@ -194,87 +188,3 @@ ManageTilePosition determineTilePosition({ return ManageTilePosition.middle; } } - -class _ButtonBar extends ConsumerWidget { - const _ButtonBar(this.snap, this.showUpdateButton); - - final Snap snap; - final bool showUpdateButton; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final l10n = AppLocalizations.of(context); - final snapLauncher = ref.watch(launchProvider(snap)); - final snapModel = ref.watch(snapModelProvider(snap.name)); - final activeChangeId = snapModel.valueOrNull?.activeChangeId; - final removeColor = Theme.of(context).colorScheme.error; - final initialWidgets = _initialWidgetOrder( - snapModel: snapModel, - snapLauncher: snapLauncher, - l10n: l10n, - activeChangeId: activeChangeId, - showUpdateButton: showUpdateButton, - ); - - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (initialWidgets.isNotEmpty) ...[ - initialWidgets.first, - const SizedBox(width: kSpacing), - ], - MenuAnchor( - menuChildren: [ - ...initialWidgets.skip(1), - SnapMenuItem( - onPressed: () => - StoreNavigator.pushSnap(context, name: snap.name), - title: l10n.managePageShowDetailsLabel, - ), - SnapMenuItem( - onPressed: ref.read(snapModelProvider(snap.name).notifier).remove, - title: SnapAction.remove.label(l10n), - textStyle: TextStyle(color: removeColor), - ), - ], - builder: (context, controller, child) => YaruOptionButton( - onPressed: controller.isOpen ? controller.close : controller.open, - child: const Icon(YaruIcons.view_more_horizontal), - ), - ), - ], - ); - } - - List _initialWidgetOrder({ - required AsyncValue snapModel, - required AppLocalizations l10n, - required SnapLauncher snapLauncher, - required bool showUpdateButton, - required String? activeChangeId, - }) { - final hasActiveChange = activeChangeId != null; - final canOpen = snapLauncher.isLaunchable; - return [ - if (hasActiveChange) - ActiveChangeStatus( - snapName: snapModel.valueOrNull?.name, - activeChangeId: activeChangeId, - ) - else ...[ - if (showUpdateButton) - UpdateButton(snapModel: snapModel, activeChangeId: activeChangeId), - if (!showUpdateButton && canOpen) - OutlinedButton( - onPressed: snapLauncher.open, - child: Text(l10n.snapActionOpenLabel), - ), - if (showUpdateButton && canOpen) - SnapMenuItem( - onPressed: snapLauncher.open, - title: l10n.snapActionOpenLabel, - ), - ], - ]; - } -} diff --git a/packages/app_center/lib/manage/snap_actions_button.dart b/packages/app_center/lib/manage/snap_actions_button.dart new file mode 100644 index 000000000..89513873b --- /dev/null +++ b/packages/app_center/lib/manage/snap_actions_button.dart @@ -0,0 +1,143 @@ +import 'package:app_center/constants.dart'; +import 'package:app_center/extensions/iterable_extensions.dart'; +import 'package:app_center/l10n.dart'; +import 'package:app_center/layout.dart'; +import 'package:app_center/manage/updates_model.dart'; +import 'package:app_center/snapd/snap_action.dart'; +import 'package:app_center/snapd/snapd.dart'; +import 'package:app_center/widgets/active_change_content.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:yaru/yaru.dart'; + +class SnapActionButtons extends ConsumerWidget { + const SnapActionButtons({ + required this.snapName, + required this.isPrimary, + super.key, + }); + + final String snapName; + final bool isPrimary; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final l10n = AppLocalizations.of(context); + final snapModel = ref.watch(snapModelProvider(snapName)); + if (!snapModel.hasValue) { + return const Center( + child: SizedBox.square( + dimension: kLoaderMediumHeight, + child: YaruCircularProgressIndicator(), + ), + ); + } + final snapData = snapModel.value!; + final shouldQuitToUpdate = snapData.localSnap?.refreshInhibit != null; + final snap = snapData.snap; + final snapViewModel = ref.watch(snapModelProvider(snap.name).notifier); + final snapLauncher = snapData.localSnap == null + ? null + : ref.watch(launchProvider(snapData.localSnap!)); + final canOpen = snapLauncher?.isLaunchable ?? false; + final hasActiveChange = snapData.activeChangeId != null; + final hasUpdate = ref.watch(hasUpdateProvider(snap.name)); + + final SnapAction? primaryAction; + if (snapData.isInstalled) { + final hasChangedChannel = snapData.selectedChannel != null && + snapData.localSnap!.trackingChannel != null && + snapData.selectedChannel != snapData.localSnap!.trackingChannel; + + if (hasChangedChannel) { + primaryAction = SnapAction.switchChannel; + } else if (!shouldQuitToUpdate && hasUpdate) { + primaryAction = SnapAction.update; + } else if (canOpen) { + primaryAction = SnapAction.open; + } else { + primaryAction = null; + } + } else { + primaryAction = SnapAction.install; + } + + final secondaryActions = [ + if (canOpen) SnapAction.open, + if (!shouldQuitToUpdate && hasUpdate) SnapAction.update, + if (snapData.isInstalled) SnapAction.remove, + ]..remove(primaryAction ?? SnapAction.open); + + final secondaryActionsWidgets = [ + ...secondaryActions.map((action) { + final color = action == SnapAction.remove + ? Theme.of(context).colorScheme.error + : null; + return PopupMenuItem( + onTap: action.callback(snapData, snapViewModel, snapLauncher), + child: IntrinsicWidth( + child: ListTile( + mouseCursor: SystemMouseCursors.click, + title: Text( + action.label(l10n), + style: TextStyle(color: color), + ), + ), + ), + ); + }), + ]; + + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (hasActiveChange) + ActiveChangeStatus( + snapName: snap.name, + activeChangeId: snapData.activeChangeId!, + ) + else ...[ + if (shouldQuitToUpdate) const QuitToUpdateNotice(), + (isPrimary ? YaruSplitButton.new : YaruSplitButton.outlined.call)( + items: [ + if (snapData.isInstalled && snapData.activeChangeId == null) + ...secondaryActionsWidgets, + ], + onPressed: snapData.activeChangeId == null + ? primaryAction?.callback(snapData, snapViewModel, snapLauncher) + : null, + child: Text( + primaryAction?.label(l10n) ?? SnapAction.open.label(l10n), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ].separatedBy(const SizedBox(width: kSpacing)), + ); + } +} + +@visibleForTesting +class QuitToUpdateNotice extends StatelessWidget { + const QuitToUpdateNotice({super.key}); + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context); + final colorScheme = Theme.of(context).colorScheme; + + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(YaruIcons.warning_filled, color: colorScheme.warning), + const SizedBox(width: 8), + Text( + l10n.managePageQuitToUpdate, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ); + } +} diff --git a/packages/app_center/lib/manage/update_button.dart b/packages/app_center/lib/manage/update_button.dart deleted file mode 100644 index 741fef8a4..000000000 --- a/packages/app_center/lib/manage/update_button.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'package:app_center/l10n.dart'; -import 'package:app_center/layout.dart'; -import 'package:app_center/snapd/snapd.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:yaru/yaru.dart'; - -class UpdateButton extends ConsumerWidget { - const UpdateButton({ - required this.snapModel, - required this.activeChangeId, - super.key, - }); - - final AsyncValue snapModel; - final String? activeChangeId; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final l10n = AppLocalizations.of(context); - final shouldQuitToUpdate = - snapModel.valueOrNull?.localSnap?.refreshInhibit != null; - final snap = - snapModel.valueOrNull?.localSnap ?? snapModel.valueOrNull?.storeSnap; - - if (shouldQuitToUpdate) { - return const QuitToUpdateNotice(); - } else { - return OutlinedButton( - onPressed: activeChangeId != null || !snapModel.hasValue - ? null - : ref.read(snapModelProvider(snap!.name).notifier).refresh, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(YaruIcons.download), - const SizedBox(width: kSpacingSmall), - Text( - l10n.snapActionUpdateLabel, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ], - ), - ); - } - } -} - -@visibleForTesting -class QuitToUpdateNotice extends StatelessWidget { - const QuitToUpdateNotice({super.key}); - - @override - Widget build(BuildContext context) { - final l10n = AppLocalizations.of(context); - final colorScheme = Theme.of(context).colorScheme; - - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(YaruIcons.warning_filled, color: colorScheme.warning), - const SizedBox(width: 8), - Text( - l10n.managePageQuitToUpdate, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodyMedium, - ), - ], - ); - } -} diff --git a/packages/app_center/lib/snapd/snap_model.dart b/packages/app_center/lib/snapd/snap_model.dart index d3a0d5993..8ff19a67d 100644 --- a/packages/app_center/lib/snapd/snap_model.dart +++ b/packages/app_center/lib/snapd/snap_model.dart @@ -116,7 +116,7 @@ class SnapModel extends _$SnapModel { /// Updates the version of the snap. /// /// Returns `true` if the snap was updated, `false` otherwise. - Future refresh() async { + Future refresh({bool removeFromList = false}) async { assert( state.hasStoreSnap, 'The snap must be loaded from the store before updating it', @@ -132,7 +132,15 @@ class SnapModel extends _$SnapModel { SnapConfinement.classic, ); _updateChangeId(changeId); - return _listenUntilDone(changeId, ref); + return _listenUntilDone(changeId, ref).then((completedSuccessfully) { + if (removeFromList && completedSuccessfully) { + ref.read(updatesModelProvider.notifier).removeFromList(snapData.name); + ref + .read(filteredLocalSnapsProvider.notifier) + .addToList(snapData.localSnap!); + } + return completedSuccessfully; + }); } /// Uninstalls the snap. diff --git a/packages/app_center/lib/snapd/snap_page.dart b/packages/app_center/lib/snapd/snap_page.dart index 9e5a5c992..b05642067 100644 --- a/packages/app_center/lib/snapd/snap_page.dart +++ b/packages/app_center/lib/snapd/snap_page.dart @@ -1,13 +1,11 @@ import 'package:app_center/constants.dart'; import 'package:app_center/error/error.dart'; -import 'package:app_center/extensions/iterable_extensions.dart'; import 'package:app_center/extensions/string_extensions.dart'; import 'package:app_center/l10n.dart'; import 'package:app_center/layout.dart'; import 'package:app_center/manage/local_snap_providers.dart'; -import 'package:app_center/manage/update_button.dart'; +import 'package:app_center/manage/snap_actions_button.dart'; import 'package:app_center/ratings/ratings.dart'; -import 'package:app_center/snapd/snap_action.dart'; import 'package:app_center/snapd/snap_report.dart'; import 'package:app_center/snapd/snapd.dart'; import 'package:app_center/snapd/snapd_cache.dart'; @@ -15,7 +13,6 @@ import 'package:app_center/store/store_app.dart'; import 'package:app_center/widgets/shimmer_placeholder.dart'; import 'package:app_center/widgets/widgets.dart'; import 'package:app_center_ratings_client/app_center_ratings_client.dart'; -import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_html/flutter_html.dart'; @@ -177,8 +174,15 @@ class _SnapView extends StatelessWidget { const SizedBox(width: kSpacing), ], Flexible( - child: _SnapActionButtons(snapData: snapData), + child: SnapActionButtons( + snapName: snapData.name, + isPrimary: true, + ), ), + if (snapData.isInstalled) ...[ + const SizedBox(width: kSpacing), + _RatingsActionButtons(snap: snapData.snap), + ], ], ), const SizedBox(height: 32), @@ -302,7 +306,6 @@ class _SnapInfoBar extends ConsumerWidget { ), ), ); - return AppInfoBar( appInfos: [if (ratings != null) ratings, ...snapInfos], layout: layout, @@ -310,119 +313,11 @@ class _SnapInfoBar extends ConsumerWidget { } } -class _SnapActionButtons extends ConsumerWidget { - const _SnapActionButtons({required this.snapData}); - - final SnapData snapData; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final l10n = AppLocalizations.of(context); - final localSnap = snapData.localSnap; - final snapLauncher = snapData.isInstalled && localSnap != null - ? ref.watch(launchProvider(localSnap)) - : null; - final snapModel = ref.watch(snapModelProvider(snapData.name).notifier); - - final SnapAction primaryAction; - if (snapData.isInstalled) { - primaryAction = - snapData.selectedChannel == snapData.localSnap!.trackingChannel || - snapData.storeSnap == null - ? SnapAction.open - : SnapAction.switchChannel; - } else { - primaryAction = SnapAction.install; - } - - final hasActiveChange = snapData.activeChangeId != null; - final primaryActionButton = Flexible( - child: PushButton.elevated( - onPressed: primaryAction.callback(snapData, snapModel, snapLauncher), - child: Text( - primaryAction.label(l10n), - overflow: TextOverflow.ellipsis, - ), - ), - ); - - final secondaryActions = [ - ( - action: SnapAction.update, - widget: UpdateButton( - snapModel: ref.watch(snapModelProvider(snapData.name)), - activeChangeId: snapData.activeChangeId, - ), - ), - (action: SnapAction.remove, widget: null), - ]; - final secondaryActionsButton = MenuAnchor( - menuChildren: [ - ...secondaryActions.map((entry) { - final (:action, :widget) = entry; - final color = action == SnapAction.remove - ? Theme.of(context).colorScheme.error - : null; - return widget == null - ? MenuItemButton( - onPressed: action.callback(snapData, snapModel, snapLauncher), - child: IntrinsicWidth( - child: ListTile( - mouseCursor: SystemMouseCursors.click, - title: Text( - action.label(l10n), - style: TextStyle(color: color), - ), - ), - ), - ) - : IntrinsicWidth( - child: ListTile( - mouseCursor: SystemMouseCursors.click, - title: widget, - ), - ); - }), - ], - builder: (context, controller, child) => YaruOptionButton( - onPressed: () { - if (controller.isOpen) { - controller.close(); - } else { - controller.open(); - } - }, - child: const Icon(YaruIcons.view_more_horizontal), - ), - ); - - return hasActiveChange - ? ActiveChangeStatus( - snapName: snapModel.snapName, - activeChangeId: snapData.activeChangeId!, - ) - : Row( - mainAxisSize: MainAxisSize.min, - children: [ - primaryActionButton, - if (snapData.isInstalled && snapData.activeChangeId == null) - secondaryActionsButton, - if (snapData.isInstalled) - _RatingsActionButtons( - snap: snapData.snap, - ), - ] - .whereNotNull() - .toList() - .separatedBy(const SizedBox(width: kSpacingSmall)), - ); - } -} - class _RatingsActionButtons extends ConsumerWidget { const _RatingsActionButtons({required this.snap}); final Snap snap; + @override Widget build(BuildContext context, WidgetRef ref) { final ratingsModel = ref.watch(ratingsModelProvider(snap.name)); @@ -574,7 +469,7 @@ class _ChannelDropdown extends ConsumerWidget { l10n.snapPageChannelLabel, style: Theme.of(context).textTheme.labelLarge, ), - const SizedBox(width: 16), + const SizedBox(width: kSpacingSmall), SizedBox( width: _kChannelDropdownWidth, child: MenuButtonBuilder( diff --git a/packages/app_center/lib/widgets/active_change_content.dart b/packages/app_center/lib/widgets/active_change_content.dart index 8cf3dbdf4..e85788423 100644 --- a/packages/app_center/lib/widgets/active_change_content.dart +++ b/packages/app_center/lib/widgets/active_change_content.dart @@ -41,7 +41,7 @@ class _ActiveChangeText extends ConsumerWidget { return Row( children: [ SizedBox.square( - dimension: kCircularProgressIndicatorHeight, + dimension: kLoaderHeight, child: YaruCircularProgressIndicator( value: change?.progress, strokeWidth: 2, diff --git a/packages/app_center/pubspec.yaml b/packages/app_center/pubspec.yaml index c731dc0ad..1d6f0a359 100644 --- a/packages/app_center/pubspec.yaml +++ b/packages/app_center/pubspec.yaml @@ -49,10 +49,10 @@ dependencies: ubuntu_logger: ^0.2.0 ubuntu_service: ^0.4.0 ubuntu_test: ^0.2.1 - ubuntu_widgets: ^0.7.0 + ubuntu_widgets: ^0.7.1 url_launcher: ^6.3.0 xdg_directories: ^1.0.4 - yaru: ^5.2.1 + yaru: ^5.3.0 yaru_test: ^0.2.0 yaru_window: ^0.2.1 @@ -68,7 +68,7 @@ dev_dependencies: json_serializable: ^6.8.0 mockito: ^5.4.4 riverpod_generator: ^2.4.3 - ubuntu_lints: ^0.4.0 + ubuntu_lints: ^0.4.1 flutter: uses-material-design: true diff --git a/packages/app_center/test/manage_page_test.dart b/packages/app_center/test/manage_page_test.dart index 7564edb54..b74fb366c 100644 --- a/packages/app_center/test/manage_page_test.dart +++ b/packages/app_center/test/manage_page_test.dart @@ -1,7 +1,7 @@ import 'package:app_center/constants.dart'; import 'package:app_center/manage/local_snap_providers.dart'; import 'package:app_center/manage/manage.dart'; -import 'package:app_center/manage/update_button.dart'; +import 'package:app_center/manage/snap_actions_button.dart'; import 'package:app_center/manage/updates_model.dart'; import 'package:app_center/snapd/snapd.dart'; import 'package:app_center/widgets/widgets.dart'; @@ -28,6 +28,7 @@ void main() { createSnap( name: 'testsnap2', title: 'Another Test Snap', + type: 'Not an app', version: '1.5', channel: 'latest/candidate', ), @@ -174,10 +175,12 @@ void main() { ), ); await tester.pump(); + await tester.pump(); final testTile = find.snapTile('Test Snap'); final testTile2 = find.snapTile('Another Test Snap'); expect(testTile, findsOneWidget); + expect(testTile2, findsOneWidget); final openButton = find .descendant( @@ -193,9 +196,9 @@ void main() { .hitTestable(); expect(openButton, findsOneWidget); - expect(openButton2, findsNothing); - - expect(tester.widget(openButton).enabled, isTrue); + expect(openButton, isEnabled); + expect(openButton2, findsOneWidget); + expect(openButton2, isDisabled); await tester.tap(openButton); verify(snapLauncher.open()).called(1); @@ -264,7 +267,7 @@ void main() { await tester.pumpAndSettle(); verifyNever(snapd.refresh(any)); - await tester.tap(find.text(tester.l10n.snapActionUpdateLabel)); + await tester.tap(find.text(tester.l10n.snapActionUpdateLabel).first); verify(snapd.refresh(any, channel: anyNamed('channel'))).called(1); }); diff --git a/packages/app_center/test/snap_page_test.dart b/packages/app_center/test/snap_page_test.dart index 9a8f1a459..55ccb470f 100644 --- a/packages/app_center/test/snap_page_test.dart +++ b/packages/app_center/test/snap_page_test.dart @@ -160,10 +160,13 @@ void main() { ), ).called(1); - final viewMoreButton = find.byIcon(YaruIcons.view_more_horizontal); + final viewMoreButton = find.descendant( + of: find.byType(YaruSplitButton), + matching: find.byIcon(YaruIcons.pan_down), + ); expect(viewMoreButton, findsOneWidget); await tester.tap(viewMoreButton); - await tester.pump(); + await tester.pumpAndSettle(); final removeButton = find.text(tester.l10n.snapActionRemoveLabel); expect(removeButton, findsOneWidget); @@ -207,9 +210,7 @@ void main() { expectSnapInfos(tester, storeSnap, 'latest/edge'); expect(find.byType(ScreenshotGallery), findsOneWidget); expect(find.text(tester.l10n.snapActionInstallLabel), findsNothing); - - await tester.tap(find.text(tester.l10n.snapActionOpenLabel)); - verify(snapLauncher.open()).called(1); + expect(find.text(tester.l10n.snapActionUpdateLabel), findsOneWidget); await tester.tap(find.byIcon(Icons.thumb_up_outlined)); verify( @@ -229,24 +230,29 @@ void main() { ), ).called(1); - final viewMoreButton = find.byIcon(YaruIcons.view_more_horizontal); + final viewMoreButton = find.descendant( + of: find.byType(YaruSplitButton), + matching: find.byIcon(YaruIcons.pan_down), + ); expect(viewMoreButton, findsOneWidget); await tester.tap(viewMoreButton); - await tester.pump(); + await tester.pumpAndSettle(); - final updateButton = find.text(tester.l10n.snapActionUpdateLabel); - expect(updateButton, findsOneWidget); + final openButton = find.text(tester.l10n.snapActionOpenLabel); + expect(openButton, findsOneWidget); await tester.tap(find.text(tester.l10n.snapActionRemoveLabel)); - await tester.pump(); + await tester.pumpAndSettle(); verify(service.remove(any)).called(1); - final l10n = tester.l10n; expect( find.text(tester.l10n.snapRatingsVotes(snapRating.totalVotes)), findsOneWidget, ); - expect(find.text(snapRating.ratingsBand.localize(l10n)), findsOneWidget); + expect( + find.text(snapRating.ratingsBand.localize(tester.l10n)), + findsOneWidget, + ); }); testWidgets('store-only', (tester) async { @@ -326,15 +332,15 @@ void main() { verify(snapLauncher.open()).called(1); await tester.pump(); - final findMoreButton = find.byIcon(YaruIcons.view_more_horizontal); + final findMoreButton = find.byIcon(YaruIcons.pan_down); expect(findMoreButton, findsOneWidget); await tester.tap(findMoreButton); - await tester.pump(); + await tester.pumpAndSettle(); final removeButton = find.text(tester.l10n.snapActionRemoveLabel); expect(removeButton, findsOneWidget); await tester.tap(removeButton); - await tester.pump(); + await tester.pumpAndSettle(); verify(service.remove(any)).called(1); expect(find.text(tester.l10n.snapActionUpdateLabel), findsNothing); diff --git a/packages/app_center/test/test_utils.dart b/packages/app_center/test/test_utils.dart index ac491dc14..e37d56546 100644 --- a/packages/app_center/test/test_utils.dart +++ b/packages/app_center/test/test_utils.dart @@ -156,13 +156,19 @@ MockSnapdService registerMockSnapdService({ .thenAnswer((_) => Stream.value([if (storeSnap != null) storeSnap])); if (localSnap != null) { when(service.getSnap(any)).thenAnswer((_) async => localSnap); - } else { + } else if (storeSnap != null && localSnap == null) { when(service.getSnap(any)).thenThrow( SnapdException( message: 'snap not installed', kind: 'snap-not-found', ), ); + } else { + when(service.getSnap(any)).thenAnswer((invocation) async { + final name = invocation.positionalArguments.first as String; + return [...?installedSnaps, ...?refreshableSnaps] + .firstWhere((s) => s.name == name); + }); } when( service.install( diff --git a/packages/app_center_ratings_client/pubspec.yaml b/packages/app_center_ratings_client/pubspec.yaml index 7a5578c13..d99b2a75d 100644 --- a/packages/app_center_ratings_client/pubspec.yaml +++ b/packages/app_center_ratings_client/pubspec.yaml @@ -20,4 +20,4 @@ dev_dependencies: json_serializable: ^6.8.0 mockito: ^5.4.4 test: ^1.25.8 - ubuntu_lints: ^0.4.0 + ubuntu_lints: ^0.4.1