From fd2ccebafe08aafde9424276fcad338cbadded73 Mon Sep 17 00:00:00 2001 From: ivo Date: Tue, 13 May 2025 17:18:46 +0200 Subject: [PATCH] feat: add label for newly installed vms --- lib/src/pages/downloader.dart | 78 ++++++++++++------- lib/src/pages/manager.dart | 15 +++- ...manage_machines_after_download_button.dart | 46 +++++++++++ lib/src/widgets/manager/new_vm_tag.dart | 29 +++++++ pubspec.yaml | 3 +- 5 files changed, 141 insertions(+), 30 deletions(-) create mode 100644 lib/src/widgets/downloader/manage_machines_after_download_button.dart create mode 100644 lib/src/widgets/manager/new_vm_tag.dart diff --git a/lib/src/pages/downloader.dart b/lib/src/pages/downloader.dart index 9505e48..d5a43df 100644 --- a/lib/src/pages/downloader.dart +++ b/lib/src/pages/downloader.dart @@ -5,6 +5,8 @@ import 'dart:io'; import 'package:desktop_notifications/desktop_notifications.dart'; import 'package:flutter/material.dart'; import 'package:gettext_i18n/gettext_i18n.dart'; +import 'package:quickgui/src/widgets/downloader/run_image_button.dart'; +import 'package:path/path.dart' as p; import '../model/operating_system.dart'; import '../model/option.dart'; @@ -36,6 +38,7 @@ class _DownloaderState extends State { bool _downloadFinished = false; var controller = StreamController(); Process? _process; + String? _newestVmFolderName; @override void initState() { @@ -54,11 +57,26 @@ class _DownloaderState extends State { } } + Future findNewestFolder() async { + final dir = Directory.current; + final folders = await dir + .list() + .where((entity) => entity is Directory) + .cast() + .toList(); + + folders + .sort((a, b) => b.statSync().modified.compareTo(a.statSync().modified)); + + return folders.isNotEmpty ? p.basename(folders.first.path) : null; + } + Stream progressStream() { var options = [widget.operatingSystem.code, widget.version.version]; if (widget.option != null) { options.add(widget.option!.option); } + Process.start('quickget', options).then((process) { if (widget.option!.downloader != 'zsync') { process.stderr.transform(utf8.decoder).forEach(parseCurlProgress); @@ -69,25 +87,30 @@ class _DownloaderState extends State { process.exitCode.then((value) { bool _cancelled = value.isNegative; controller.close(); - setState(() { - _downloadFinished = true; - notificationsClient?.notify( - _cancelled - ? context.t('Download cancelled') - : context.t('Download complete'), - body: _cancelled - ? context.t( - 'Download of {0} has been canceled.', - args: [widget.operatingSystem.name], - ) - : context.t( - 'Download of {0} has completed.', - args: [widget.operatingSystem.name], - ), - appName: 'Quickgui', - expireTimeoutMs: 10000, /* 10 seconds */ - ); - }); + findNewestFolder().then( + (path) { + setState(() { + _downloadFinished = true; + _newestVmFolderName = path; + notificationsClient?.notify( + _cancelled + ? context.t('Download cancelled') + : context.t('Download complete'), + body: _cancelled + ? context.t( + 'Download of {0} has been canceled.', + args: [widget.operatingSystem.name], + ) + : context.t( + 'Download of {0} has completed.', + args: [widget.operatingSystem.name], + ), + appName: 'Quickgui', + expireTimeoutMs: 10000, /* 10 seconds */ + ); + }); + }, + ); }); setState(() { @@ -103,10 +126,7 @@ class _DownloaderState extends State { appBar: AppBar( title: Text( context.t('Downloading {0}', args: [ - '${widget.operatingSystem.name} ${widget.version.version}' + - (widget.option!.option.isNotEmpty - ? ' (${widget.option!.option})' - : '') + '${widget.operatingSystem.name} ${widget.version.version}${widget.option!.option.isNotEmpty ? ' (${widget.option!.option})' : ''}' ]), ), automaticallyImplyLeading: false, @@ -117,10 +137,10 @@ class _DownloaderState extends State { child: StreamBuilder( stream: _progressStream, builder: (context, AsyncSnapshot snapshot) { - var data = !snapshot.hasData || - widget.option!.downloader != 'curl' - ? null - : snapshot.data; + var data = + !snapshot.hasData || widget.option!.downloader != 'curl' + ? null + : snapshot.data; return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -143,6 +163,10 @@ class _DownloaderState extends State { }, ), ), + ManageMachinesAfterDownloadButton( + downloadFinished: _downloadFinished, + vmName: _newestVmFolderName ?? '', + ), CancelDismissButton( onCancel: () { _process?.kill(); diff --git a/lib/src/pages/manager.dart b/lib/src/pages/manager.dart index 6213562..dc4e691 100644 --- a/lib/src/pages/manager.dart +++ b/lib/src/pages/manager.dart @@ -9,6 +9,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:gettext_i18n/gettext_i18n.dart'; import 'package:path/path.dart' as path; import 'package:process_run/shell.dart'; +import 'package:quickgui/src/widgets/manager/new_vm_tag.dart'; import 'package:version/version.dart'; import '../globals.dart'; @@ -20,7 +21,9 @@ import '../model/vminfo.dart'; /// Displays a list of available VMs, running state and connection info, /// with buttons to start and stop VMs. class Manager extends StatefulWidget { - const Manager({super.key}); + const Manager({super.key, this.newVmName}); + + final String? newVmName; @override State createState() => _ManagerState(); @@ -249,6 +252,9 @@ class _ManagerState extends State with PreferencesMixin { List _buildRow(String currentVm, Color buttonColor) { final bool active = _activeVms.containsKey(currentVm); final bool sshy = _sshVms.contains(currentVm); + + final bool newVM = widget.newVmName == currentVm; + VmInfo vmInfo = VmInfo(); String connectInfo = ''; if (active) { @@ -287,7 +293,12 @@ class _ManagerState extends State with PreferencesMixin { return [ ListTile( leading: osIcon ?? const Icon(Icons.computer, size: 32), - title: Text(currentVm), + title: Row( + children: [ + Text(currentVm), + newVM ? const NewVmTag() : const SizedBox.shrink() + ], + ), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ diff --git a/lib/src/widgets/downloader/manage_machines_after_download_button.dart b/lib/src/widgets/downloader/manage_machines_after_download_button.dart new file mode 100644 index 0000000..a339a69 --- /dev/null +++ b/lib/src/widgets/downloader/manage_machines_after_download_button.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:gettext_i18n/gettext_i18n.dart'; +import 'package:quickgui/src/pages/manager.dart'; + +class ManageMachinesAfterDownloadButton extends StatelessWidget { + const ManageMachinesAfterDownloadButton({ + required this.downloadFinished, + required this.vmName, + super.key, + }); + + final bool downloadFinished; + final String vmName; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + downloadFinished + ? ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.surface, + foregroundColor: Theme.of(context).colorScheme.onSurface, + ), + onPressed: () { + Navigator.of(context).pop(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => Manager( + newVmName: vmName, + ), + ), + ); + }, + child: Text(context.t('Manage existing machines')), + ) + : const SizedBox.shrink(), + ], + ), + ); + } +} diff --git a/lib/src/widgets/manager/new_vm_tag.dart b/lib/src/widgets/manager/new_vm_tag.dart new file mode 100644 index 0000000..4d8573d --- /dev/null +++ b/lib/src/widgets/manager/new_vm_tag.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +class NewVmTag extends StatelessWidget { + const NewVmTag({super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Row( + children: [ + Icon( + Icons.new_releases, + color: Theme.of(context).colorScheme.primary, + ), + Padding( + padding: const EdgeInsets.only(left: 4.0), + child: Text( + 'new', + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + ), + ), + ), + ], + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index d890549..9a76f0f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: quickgui description: An elegant virtual machine manager for the desktop -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: "none" # Remove this line if you wish to publish to pub.dev version: 1.2.10+1 environment: @@ -15,6 +15,7 @@ dependencies: cupertino_icons: ^1.0.8 desktop_notifications: ^0.6.3 file_picker: ^8.0.6 + path: ^1.9.1 flutter_svg: ^2.0.17 gettext: ^1.2.0 gettext_i18n: ^1.0.7