Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Installing section for manage page #1810

Merged
merged 13 commits into from
Sep 11, 2024
1 change: 1 addition & 0 deletions packages/app_center/lib/layout.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';

const kMargin = 4.0;
const kMarginLarge = 24.0;
const kCardMargin = 4.0;
const kCardSpacing = 16.0;
const kPaneWidth = 204.0;
Expand Down
10 changes: 10 additions & 0 deletions packages/app_center/lib/manage/local_snap_providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ class FilteredLocalSnaps extends _$FilteredLocalSnaps {
);
}

/// Used to add a snap from the list without reloading the whole provider.
/// Should be used when a snap is uninstalled directly from the manage page
/// list for example.
Future<void> addToList(Snap snap) async {
if (!state.hasValue) return;
final localSnap = await _snapd.getSnap(snap.name);
_refreshWithFilters([...state.value!.snaps, localSnap]);
}

/// Used to remove a snap from the list without reloading the whole provider.
/// Should be used when a snap is uninstalled directly from the manage page
/// list for example.
Expand All @@ -58,6 +67,7 @@ class FilteredLocalSnaps extends _$FilteredLocalSnaps {
snap.titleOrName.toLowerCase().contains(filter) &&
(showSystemApps || snap.apps.isNotEmpty),
)
.toSet()
.sortedSnaps(sortOrder);
if (updateState) {
state = AsyncData(
Expand Down
39 changes: 34 additions & 5 deletions packages/app_center/lib/manage/manage_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:app_center/layout.dart';
import 'package:app_center/manage/local_snap_providers.dart';
import 'package:app_center/manage/manage_snap_tile.dart';
import 'package:app_center/manage/updates_model.dart';
import 'package:app_center/snapd/currently_installing_model.dart';
import 'package:app_center/snapd/snapd.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
Expand All @@ -25,6 +26,8 @@ class ManagePage extends ConsumerWidget {
final l10n = AppLocalizations.of(context);
final textTheme = Theme.of(context).textTheme;
final isLoading = updatesModel.isLoading || localSnapsModel.isLoading;
final currentlyInstalling = ref.watch(currentlyInstallingModelProvider);
final currentlyInstallingNames = currentlyInstalling.keys.toList();

if (updatesModel.hasError || localSnapsModel.hasError) {
return ErrorView(
Expand Down Expand Up @@ -87,7 +90,7 @@ class ManagePage extends ConsumerWidget {
);
},
),
const SizedBox(height: 24),
const SizedBox(height: kMarginLarge),
if (!hasInternet && !isLoading)
_MinHeightAsProgressIndicator(
child: Text(
Expand Down Expand Up @@ -132,9 +135,35 @@ class ManagePage extends ConsumerWidget {
child: Center(child: YaruCircularProgressIndicator()),
),
),
if (currentlyInstalling.isNotEmpty) ...[
SliverList.list(
children: [
const SizedBox(height: kSectionSpacing),
Text(
l10n.managePageInstallingLabel(1),
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(fontWeight: FontWeight.w500),
),
const SizedBox(height: kMarginLarge),
],
),
SliverList.builder(
itemCount: currentlyInstalling.length,
itemBuilder: (context, index) => ManageSnapTile(
snap:
currentlyInstalling[currentlyInstallingNames[index]]!.snap,
position: determineTilePosition(
index: index,
length: currentlyInstalling.length,
),
),
),
],
SliverList.list(
children: [
const SizedBox(height: 48),
const SizedBox(height: kSectionSpacing),
Text(
l10n.managePageInstalledAndUpdatedLabel,
style: Theme.of(context)
Expand Down Expand Up @@ -202,7 +231,7 @@ class ManagePage extends ConsumerWidget {
),
],
),
const SizedBox(height: 24),
const SizedBox(height: kMarginLarge),
],
),
localSnapsModel.when(
Expand Down Expand Up @@ -330,7 +359,7 @@ class _SelfUpdateInfoBox extends ConsumerWidget {
refreshInhibitModel.valueOrNull?.refreshInhibit?.proceedTime;

if (proceedTime == null) {
return const SizedBox(height: 48);
return const SizedBox(height: kSectionSpacing);
}

final buttonStyle = OutlinedButtonTheme.of(context).style?.copyWith(
Expand All @@ -344,7 +373,7 @@ class _SelfUpdateInfoBox extends ConsumerWidget {
child: YaruInfoBox(
title: Text(l10n.managePageOwnUpdateAvailable),
subtitle: Padding(
padding: const EdgeInsets.only(right: 24),
padding: const EdgeInsets.only(right: kMarginLarge),
child: Text(l10n.managePageOwnUpdateDescription),
),
trailing: Column(
Expand Down
189 changes: 83 additions & 106 deletions packages/app_center/lib/manage/manage_snap_tile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,13 @@ class ManageSnapTile extends StatelessWidget {
),
],
),
trailing: showUpdateButton
? _ButtonBarForUpdate(snap)
: _ButtonBarForOpen(snap),
trailing: SizedBox(
width: 260,
child: Align(
alignment: Alignment.centerRight,
child: _ButtonBar(snap, showUpdateButton),
),
),
),
);
}
Expand All @@ -188,10 +192,11 @@ ManageTilePosition determineTilePosition({
}
}

class _ButtonBarForUpdate extends ConsumerWidget {
const _ButtonBarForUpdate(this.snap);
class _ButtonBar extends ConsumerWidget {
const _ButtonBar(this.snap, this.showUpdateButton);

final Snap snap;
final bool showUpdateButton;

@override
Widget build(BuildContext context, WidgetRef ref) {
Expand All @@ -200,31 +205,24 @@ class _ButtonBarForUpdate extends ConsumerWidget {
final snapModel = ref.watch(snapModelProvider(snap.name));
final activeChangeId = snapModel.valueOrNull?.activeChangeId;
final removeColor = Theme.of(context).colorScheme.error;
final shouldQuitToUpdate =
snapModel.valueOrNull?.localSnap?.refreshInhibit != null;
final initialWidgets = _initialWidgetOrder(
snapModel: snapModel,
snapLauncher: snapLauncher,
l10n: l10n,
activeChangeId: activeChangeId,
showUpdateButton: showUpdateButton,
);

return Row(
mainAxisSize: MainAxisSize.min,
children: [
if (shouldQuitToUpdate)
const QuitToUpdateNotice()
else
OutlinedButton(
onPressed: activeChangeId != null || !snapModel.hasValue
? null
: ref.read(snapModelProvider(snap.name).notifier).refresh,
child: activeChangeId != null
? ActiveChangeContent(activeChangeId, showText: false)
: const _UpdateButton(),
),
const SizedBox(width: 16),
if (initialWidgets.isNotEmpty) ...[
initialWidgets.first,
const SizedBox(width: kSpacing),
],
MenuAnchor(
menuChildren: [
if (snapLauncher.isLaunchable)
SnapMenuItem(
onPressed: snapLauncher.open,
title: l10n.snapActionOpenLabel,
),
...initialWidgets.skip(1),
SnapMenuItem(
onPressed: () =>
StoreNavigator.pushSnap(context, name: snap.name),
Expand All @@ -237,106 +235,85 @@ class _ButtonBarForUpdate extends ConsumerWidget {
),
],
builder: (context, controller, child) => YaruOptionButton(
onPressed: () {
if (controller.isOpen) {
controller.close();
} else {
controller.open();
}
},
onPressed: controller.isOpen ? controller.close : controller.open,
child: const Icon(YaruIcons.view_more_horizontal),
),
),
],
);
}

List<Widget> _initialWidgetOrder({
required AsyncValue<SnapData> 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,
),
],
];
}
}

class _ButtonBarForOpen extends ConsumerWidget {
const _ButtonBarForOpen(this.snap);
class _UpdateButton extends ConsumerWidget {
const _UpdateButton({
required this.snapModel,
required this.activeChangeId,
});

final Snap snap;
final AsyncValue<SnapData> snapModel;
final String? activeChangeId;

@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 shouldQuitToUpdate =
snapModel.valueOrNull?.localSnap?.refreshInhibit != null;
final snap =
snapModel.valueOrNull?.localSnap ?? snapModel.valueOrNull?.storeSnap;

return Row(
mainAxisSize: MainAxisSize.min,
children: [
Stack(
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: [
if (activeChangeId != null)
OutlinedButton(
onPressed: null,
child: ActiveChangeContent(activeChangeId, showText: false),
),
Visibility(
maintainSize: true,
maintainAnimation: true,
maintainState: true,
visible: snapLauncher.isLaunchable && activeChangeId == null,
child: OutlinedButton(
onPressed: snapLauncher.open,
child: Text(
l10n.snapActionOpenLabel,
),
),
const Icon(YaruIcons.download),
const SizedBox(width: kSpacingSmall),
Text(
l10n.snapActionUpdateLabel,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
const SizedBox(width: 16),
MenuAnchor(
menuChildren: [
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: () {
if (controller.isOpen) {
controller.close();
} else {
controller.open();
}
},
child: const Icon(YaruIcons.view_more_horizontal),
),
),
],
);
}
}

class _UpdateButton extends StatelessWidget {
const _UpdateButton();

@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(YaruIcons.download),
const SizedBox(width: 8),
Text(
l10n.snapActionUpdateLabel,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
);
);
}
}
}

Expand Down
12 changes: 12 additions & 0 deletions packages/app_center/lib/manage/updates_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,18 @@ class UpdatesModel extends _$UpdatesModel {
);
}

/// Used to add a snap from the list without reloading the whole provider.
/// Should be used when a snap is uninstalled directly from the manage page
/// list for example.
void addToList(Snap snap) {
if (!state.hasValue) return;
state = AsyncData(
state.value!.copyWith(
snaps: state.value!.snaps.toList()..add(snap),
),
);
}

/// Used to remove a snap from the list without reloading the whole provider.
/// Should be used when a snap is uninstalled directly from the manage page
/// list for example.
Expand Down
Loading
Loading