Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/src/globals.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ var gIsSnap = Platform.environment['SNAP']?.isNotEmpty ?? false;
const String prefWorkingDirectory = 'workingDirectory';
const String prefThemeMode = 'themeMode';
const String prefCurrentLocale = 'currentLocale';
const String prefNewlyInstalledVms = 'newlyInstalledVms';

Future<String> fetchQuickemuVersion() async {
// Get the version of quickemu
Expand Down
2 changes: 1 addition & 1 deletion lib/src/mixins/preferences_mixin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ mixin PreferencesMixin {
return prefs.getInt(key) as T;
} else if (T == String) {
return prefs.getString(key) as T;
} else if (T == List) {
} else if (T == List<String>) {
return prefs.getStringList(key) as T;
}
}
Expand Down
51 changes: 42 additions & 9 deletions lib/src/pages/downloader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ 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/globals.dart';
import 'package:quickgui/src/widgets/downloader/manage_machines_after_download_button.dart';
import 'package:path/path.dart' as p;
import 'package:quickgui/src/mixins/preferences_mixin.dart';

import '../model/operating_system.dart';
import '../model/option.dart';
Expand All @@ -29,7 +33,7 @@ class Downloader extends StatefulWidget {
_DownloaderState createState() => _DownloaderState();
}

class _DownloaderState extends State<Downloader> {
class _DownloaderState extends State<Downloader> with PreferencesMixin {
final notificationsClient = Platform.isMacOS ? null : NotificationsClient();
final curlPattern = RegExp("( [0-9.]+%)");
late final Stream<double> _progressStream;
Expand All @@ -54,11 +58,37 @@ class _DownloaderState extends State<Downloader> {
}
}

Future<void> addNewestVmToPrefs() async {
final dir = Directory.current;
final folders = await dir
.list()
.where((entity) => entity is Directory)
.cast<Directory>()
.toList();

folders
.sort((a, b) => b.statSync().modified.compareTo(a.statSync().modified));

// Update the prefNewlyInstalledVms with the newest folder
if (folders.isNotEmpty) {
String newestFolder = p.basename(folders.first.path);

final List<String> newVmNames =
await getPreference<List<String>>(prefNewlyInstalledVms) ??
<String>[];

newVmNames.add(newestFolder);

savePreference(prefNewlyInstalledVms, newVmNames);
}
}

Stream<double> 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);
Expand All @@ -68,9 +98,12 @@ class _DownloaderState extends State<Downloader> {

process.exitCode.then((value) {
bool _cancelled = value.isNegative;

controller.close();
addNewestVmToPrefs();
setState(() {
_downloadFinished = true;

notificationsClient?.notify(
_cancelled
? context.t('Download cancelled')
Expand Down Expand Up @@ -103,10 +136,7 @@ class _DownloaderState extends State<Downloader> {
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,
Expand All @@ -117,10 +147,10 @@ class _DownloaderState extends State<Downloader> {
child: StreamBuilder(
stream: _progressStream,
builder: (context, AsyncSnapshot<double> 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: [
Expand All @@ -143,6 +173,9 @@ class _DownloaderState extends State<Downloader> {
},
),
),
ManageMachinesAfterDownloadButton(
downloadFinished: _downloadFinished,
),
CancelDismissButton(
onCancel: () {
_process?.kill();
Expand Down
19 changes: 18 additions & 1 deletion lib/src/pages/manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -53,12 +54,14 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
'xterm',
];
Timer? refreshTimer;
List<String> _newVmNames = <String>[];

@override
void initState() {
super.initState();
_getTerminalEmulator();
_detectSpice();
_setNewVmName();
getPreference<String>(prefWorkingDirectory).then((pref) {
setState(() {
if (pref == null) {
Expand All @@ -80,6 +83,13 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
super.dispose();
}

Future<void> _setNewVmName() async {
_newVmNames =
await getPreference<List<String>>(prefNewlyInstalledVms) ?? <String>[];
// Delete prefNewlyInstalledVms to only display once
savePreference(prefNewlyInstalledVms, <String>[]);
}

void _getTerminalEmulator() async {
// Find out which terminal emulator we have set as the default.
String result = whichSync('x-terminal-emulator') ?? '';
Expand Down Expand Up @@ -249,6 +259,8 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
List<Widget> _buildRow(String currentVm, Color buttonColor) {
final bool active = _activeVms.containsKey(currentVm);
final bool sshy = _sshVms.contains(currentVm);
final bool newVM = _newVmNames.contains(currentVm);

VmInfo vmInfo = VmInfo();
String connectInfo = '';
if (active) {
Expand Down Expand Up @@ -287,7 +299,12 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
return <Widget>[
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: <Widget>[
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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,
super.key,
});

final bool downloadFinished;

@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) => const Manager(),
),
);
},
child: Text(context.t('Manage existing machines')),
)
: const SizedBox.shrink(),
],
),
);
}
}
20 changes: 20 additions & 0 deletions lib/src/widgets/manager/new_vm_tag.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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.fiber_new,
color: Theme.of(context).colorScheme.primary,
),
],
),
);
}
}
Loading
Loading