Merge branch 'quickemu-project:main' into main
This commit is contained in:
commit
7db31f7e90
|
|
@ -32,3 +32,5 @@ list.csv
|
|||
*.iso
|
||||
*.conf
|
||||
*.mo
|
||||
RELEASING.md
|
||||
BUILDING.md
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
[submodule "assets/quickemu-icons"]
|
||||
path = assets/quickemu-icons
|
||||
url = https://github.com/quickemu-project/quickemu-icons.git
|
||||
branch = quickgui
|
||||
|
|
@ -1 +0,0 @@
|
|||
flutter pub add window_size --git-url git://github.com/google/flutter-desktop-embedding.git --git-path plugins/window_size
|
||||
4
Makefile
4
Makefile
|
|
@ -5,7 +5,7 @@ BASE_NAME := quickgui-$(VERSION)
|
|||
BUILD_DIR := $(BUILD_ROOT)/$(BASE_NAME)
|
||||
BIN_TAR := $(BUILD_ROOT)/$(BASE_NAME).tar
|
||||
SRC_TAR := $(BUILD_ROOT)/$(BASE_NAME)-src.tar
|
||||
FLUTTER := /usr/local/bin/flutter
|
||||
FLUTTER := /opt/flutter/bin/flutter
|
||||
|
||||
all: version bin
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ bin: quickgui
|
|||
mkdir -p $(BUILD_DIR)
|
||||
cp -a build/linux/x64/release/bundle/* $(BUILD_DIR)
|
||||
cp -a assets/resources $(BUILD_DIR)
|
||||
tar -C $(BUILD_ROOT) -c -v -f $(BIN_TAR) $(BASE_NAME)
|
||||
tar -C $(BUILD_ROOT) -c -v --exclude "quickemu-icons/*.png" -f $(BIN_TAR) $(BASE_NAME)
|
||||
xz -z $(BIN_TAR)
|
||||
|
||||
src:
|
||||
|
|
|
|||
23
RELEASING.md
23
RELEASING.md
|
|
@ -1,23 +0,0 @@
|
|||
* bump the version number in pubspec.yaml (ex: version: 1.0.5)
|
||||
* push to github
|
||||
* create and push version tag (ex: v1.0.5)
|
||||
* create a release on GitHub
|
||||
* build the tarball (`make`)
|
||||
* upload the tarball to github
|
||||
|
||||
* `cd /mnt/data/dev/debianpackages/quickgui.deb/quickgui`
|
||||
* `head debian/changelog` to check the latest changelog
|
||||
* `dch -v a.b.c "New changelog message"`
|
||||
* `nano debian/changelog` to change the release (ex: 1.0.5-1~focal1.0 focal)
|
||||
* `uscan --noconf --force-download --rename --download-current-version --destdir=.. --verbose` to download the tarball properly
|
||||
* `dpkg-buildpackage -d -S -sa` to build the changes file (the name of the key to use is in ~/.config/dpkg/buildpackage.conf)
|
||||
* `nano debian/changelog` to change the release (ex: 1.0.5-1~hirsute1.0 hirsute)
|
||||
* `dpkg-buildpackage -d -S -sa`
|
||||
* `nano debian/changelog` to change the release (ex: 1.0.5-1~impish1.0 impish)
|
||||
* `dpkg-buildpackage -d -S -sa`
|
||||
* `nano debian/changelog` to change the release (ex: 1.0.5-1~jammy.0 jammy)
|
||||
* `dpkg-buildpackage -d -S -sa`
|
||||
* `dput ppa:yannick-mauray/quickgui ../quickgui_1.0.5-1~focal1.0_source.changes`
|
||||
* `dput ppa:yannick-mauray/quickgui ../quickgui_1.0.5-1~hirsute1.0_source.changes`
|
||||
* `dput ppa:yannick-mauray/quickgui ../quickgui_1.0.5-1~impish1.0_source.changes`
|
||||
* `dput ppa:yannick-mauray/quickgui ../quickgui_1.0.5-1~jammy1.0_source.changes`
|
||||
|
|
@ -2,7 +2,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: quickgui\n"
|
||||
"POT-Creation-Date: 2021-09-29 09:59+0200\n"
|
||||
"PO-Revision-Date: 2021-11-11 16:42+0100\n"
|
||||
"PO-Revision-Date: 2022-02-24 23:38+0100\n"
|
||||
"Last-Translator: Yannick Mauray\n"
|
||||
"Language-Team: \n"
|
||||
"Language: en\n"
|
||||
|
|
@ -69,6 +69,9 @@ msgstr "Downloading...{0}%"
|
|||
msgid "Downloading {0}"
|
||||
msgstr "Downloading {0}"
|
||||
|
||||
msgid "Download cancelled"
|
||||
msgstr "Download cancelled"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Download complete"
|
||||
msgstr "Download complete"
|
||||
|
|
@ -77,6 +80,10 @@ msgstr "Download complete"
|
|||
msgid "Download of {0} has completed."
|
||||
msgstr "Download of {0} has completed."
|
||||
|
||||
#, fuzzy
|
||||
msgid "Download of {0} has been cancelled."
|
||||
msgstr "Download of {0} has been cancelled."
|
||||
|
||||
#, fuzzy
|
||||
msgid "Manage existing machines"
|
||||
msgstr "Manage existing machines"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,166 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: quickgui\n"
|
||||
"POT-Creation-Date: 2021-09-29 09:59+0200\n"
|
||||
"PO-Revision-Date: 2021-12-19 19:23+0100\n"
|
||||
"Last-Translator: José Miguel Manzano\n"
|
||||
"Language-Team: \n"
|
||||
"Language: es\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 3.0\n"
|
||||
"X-Poedit-Basepath: .\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Quickgui : a Flutter frontend for Quickget and Quickemu"
|
||||
msgstr "Quickgui : una aplicación Flutter para Quickget y Quickemu"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Main menu"
|
||||
msgstr "Menu principal"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Use dark mode"
|
||||
msgstr "Utilizar el modo oscuro"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Cancel"
|
||||
msgstr "Cancelar"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Click to use SPICE display"
|
||||
msgstr "Pulsa para utilizar SPICE display"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Create new machines"
|
||||
msgstr "Crear nuevas máquinas"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Dismiss"
|
||||
msgstr "Descartar"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Done !"
|
||||
msgstr "Hecho !"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Download"
|
||||
msgstr "Descargar"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Download finished."
|
||||
msgstr "Descarga completa."
|
||||
|
||||
#, fuzzy
|
||||
msgid "Downloader"
|
||||
msgstr "Descargas"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Downloading (no progress available)..."
|
||||
msgstr "Descargando (progreso no disponible)..."
|
||||
|
||||
#, fuzzy
|
||||
msgid "Downloading...{0}%"
|
||||
msgstr "Descargando...{0}%"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Downloading {0}"
|
||||
msgstr "Descargando {0}"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Download complete"
|
||||
msgstr "Descarga completa"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Download of {0} has completed."
|
||||
msgstr "La descarga de {0} se ha completado."
|
||||
|
||||
#, fuzzy
|
||||
msgid "Manage existing machines"
|
||||
msgstr "Gestionar máquinas existentes"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Manager"
|
||||
msgstr "Gestionar"
|
||||
|
||||
#, fuzzy
|
||||
msgid "{0} Mbs downloaded"
|
||||
msgstr "{0} Mbs descargados"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Now run {0} to start the VM"
|
||||
msgstr "Ahora ejecuta {0} para iniciar la VM"
|
||||
|
||||
#, fuzzy
|
||||
msgid "OK"
|
||||
msgstr "OK"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Operating system"
|
||||
msgstr "Sistema operativo"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Search operating system"
|
||||
msgstr "Buscar sistema operativo"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Search option"
|
||||
msgstr "Opción de busqueda"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Select operating system"
|
||||
msgstr "Selecciona sistema operativo"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Select option"
|
||||
msgstr "Selecciona opción"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Select version for {0}"
|
||||
msgstr "Selecciona versión para {0}"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Select..."
|
||||
msgstr "Selecciona..."
|
||||
|
||||
#, fuzzy
|
||||
msgid "SPICE port"
|
||||
msgstr "Puerto SPICE"
|
||||
|
||||
#, fuzzy
|
||||
msgid "SSH port"
|
||||
msgstr "Puerto SSH"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Stop The Virtual Machine?"
|
||||
msgstr "¿Detener la Máquina Virtual?"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Target folder : {0}"
|
||||
msgstr "Directorio de destino : {0}"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Use SPICE display"
|
||||
msgstr "Usar SPICE display"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Using SPICE display"
|
||||
msgstr "Usando SPICE display"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Version"
|
||||
msgstr "Versión"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Waiting for download to start"
|
||||
msgstr "Esperando que comience la descarga"
|
||||
|
||||
#, fuzzy
|
||||
msgid "You are about to terminate the virtual machine {0}"
|
||||
msgstr "Estás a punto de cerrar la máquina virtual {0}"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Language"
|
||||
msgstr "Lenguaje"
|
||||
|
|
@ -2,7 +2,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: quickgui\n"
|
||||
"POT-Creation-Date: 2021-09-29 09:59+0200\n"
|
||||
"PO-Revision-Date: 2021-12-02 20:23+0000\n"
|
||||
"PO-Revision-Date: 2022-02-24 23:39+0100\n"
|
||||
"Last-Translator: Yannick Mauray\n"
|
||||
"Language-Team: Yannick Mauray\n"
|
||||
"Language: fr\n"
|
||||
|
|
@ -80,6 +80,9 @@ msgstr "Téléchargement terminé"
|
|||
msgid "Download of {0} has completed."
|
||||
msgstr "Le téléchargement de {0} est terminé."
|
||||
|
||||
msgid "Download of {0} has been cancelled."
|
||||
msgstr "Le téléchargement de {0} a été annulé."
|
||||
|
||||
#, fuzzy
|
||||
msgid "Manage existing machines"
|
||||
msgstr "Gérer les machines existantes"
|
||||
|
|
|
|||
|
|
@ -64,6 +64,9 @@ msgstr ""
|
|||
msgid "Download of {0} has completed."
|
||||
msgstr ""
|
||||
|
||||
msgid "Download of {0} has been cancelled."
|
||||
msgstr ""
|
||||
|
||||
msgid "Manage existing machines"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 1fab2a77f7465dd163fcb0087c2927d663d151bb
|
||||
|
|
@ -2,7 +2,6 @@ import 'dart:io';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:quickgui/src/globals.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:window_size/window_size.dart';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -32,8 +31,10 @@ class _AppState extends State<App> with PreferencesMixin {
|
|||
appSettings.setActiveLocaleSilently(
|
||||
snapshot.data?.getString(prefCurrentLocale) ??
|
||||
Platform.localeName);
|
||||
appSettings.useDarkModeSilently =
|
||||
snapshot.data!.getBool(prefThemeMode) as bool;
|
||||
var pref = snapshot.data!.getBool(prefThemeMode);
|
||||
if (pref != null) {
|
||||
appSettings.useDarkModeSilently = pref;
|
||||
}
|
||||
return Consumer<AppSettings>(
|
||||
builder: (context, appSettings, _) => MaterialApp(
|
||||
theme: ThemeData(primarySwatch: Colors.pink),
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ class _DownloaderState extends State<Downloader> {
|
|||
final notificationsClient = NotificationsClient();
|
||||
final wgetPattern = RegExp("( [0-9.]+%)");
|
||||
final macRecoveryPattern = RegExp("([0-9]+\\.[0-9])");
|
||||
final ariaPattern = RegExp("([0-9.]+%)");
|
||||
late final Stream<double> _progressStream;
|
||||
bool _downloadFinished = false;
|
||||
var controller = StreamController<double>();
|
||||
|
|
@ -55,6 +56,17 @@ class _DownloaderState extends State<Downloader> {
|
|||
}
|
||||
}
|
||||
|
||||
void parseAriaProgress(String line) {
|
||||
var matches = ariaPattern.allMatches(line).toList();
|
||||
if (matches.isNotEmpty) {
|
||||
var percent = matches[0].group(1);
|
||||
if (percent != null) {
|
||||
var value = double.parse(percent.replaceAll('%', '')) / 100.0;
|
||||
controller.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void parseMacRecoveryProgress(String line) {
|
||||
var matches = macRecoveryPattern.allMatches(line).toList();
|
||||
if (matches.isNotEmpty) {
|
||||
|
|
@ -77,7 +89,11 @@ class _DownloaderState extends State<Downloader> {
|
|||
} else if (widget.option!.downloader == 'zsync') {
|
||||
controller.add(-1);
|
||||
} else if (widget.option!.downloader == 'macrecovery') {
|
||||
process.stdout.transform(utf8.decoder).forEach(parseMacRecoveryProgress);
|
||||
process.stdout
|
||||
.transform(utf8.decoder)
|
||||
.forEach(parseMacRecoveryProgress);
|
||||
} else if (widget.option!.downloader == 'aria2c') {
|
||||
process.stderr.transform(utf8.decoder).forEach(parseAriaProgress);
|
||||
}
|
||||
|
||||
process.exitCode.then((value) {
|
||||
|
|
@ -86,13 +102,18 @@ class _DownloaderState extends State<Downloader> {
|
|||
setState(() {
|
||||
_downloadFinished = true;
|
||||
notificationsClient.notify(
|
||||
_cancelled ? context.t('Download cancelled') : context.t('Download complete'),
|
||||
_cancelled
|
||||
? context.t('Download cancelled')
|
||||
: context.t('Download complete'),
|
||||
body: _cancelled
|
||||
? context.t(
|
||||
'Download of {0} has completed.',
|
||||
'Download of {0} has been canceled.',
|
||||
args: [widget.operatingSystem.name],
|
||||
)
|
||||
: context.t('Download cancelled.'),
|
||||
: context.t(
|
||||
'Download of {0} has completed.',
|
||||
args: [widget.operatingSystem.name],
|
||||
),
|
||||
appName: 'Quickgui',
|
||||
expireTimeoutMs: 10000, /* 10 seconds */
|
||||
);
|
||||
|
|
@ -111,8 +132,12 @@ class _DownloaderState extends State<Downloader> {
|
|||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
context.t('Downloading {0}',
|
||||
args: ['${widget.operatingSystem.name} ${widget.version.version}' + (widget.option!.option.isNotEmpty ? ' (${widget.option!.option})' : '')]),
|
||||
context.t('Downloading {0}', args: [
|
||||
'${widget.operatingSystem.name} ${widget.version.version}' +
|
||||
(widget.option!.option.isNotEmpty
|
||||
? ' (${widget.option!.option})'
|
||||
: '')
|
||||
]),
|
||||
),
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
|
|
@ -122,7 +147,11 @@ class _DownloaderState extends State<Downloader> {
|
|||
child: StreamBuilder(
|
||||
stream: _progressStream,
|
||||
builder: (context, AsyncSnapshot<double> snapshot) {
|
||||
var data = !snapshot.hasData || widget.option!.downloader != 'wget' ? null : snapshot.data;
|
||||
var data = !snapshot.hasData ||
|
||||
widget.option!.downloader != 'wget' ||
|
||||
widget.option!.downloader != 'aria2c'
|
||||
? null
|
||||
: snapshot.data;
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
|
|
@ -137,7 +166,8 @@ class _DownloaderState extends State<Downloader> {
|
|||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 32),
|
||||
child: Text(context.t('Target folder : {0}', args: [Directory.current.path])),
|
||||
child: Text(context.t('Target folder : {0}',
|
||||
args: [Directory.current.path])),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ class _MainPageState extends State<MainPage> {
|
|||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
setWindowTitle(context.t('Quickgui : a Flutter frontend for Quickget and Quickemu'));
|
||||
setWindowTitle(
|
||||
context.t('Quickgui : a Flutter frontend for Quickget and Quickemu'));
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -61,7 +61,8 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
|
|||
}
|
||||
Directory.current = pref;
|
||||
});
|
||||
Future.delayed(Duration.zero, () => _getVms(context)); // Reload VM list when we enter the page.
|
||||
Future.delayed(Duration.zero,
|
||||
() => _getVms(context)); // Reload VM list when we enter the page.
|
||||
});
|
||||
refreshTimer = Timer.periodic(const Duration(seconds: 5), (Timer t) {
|
||||
_getVms(context);
|
||||
|
|
@ -78,7 +79,8 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
|
|||
// Find out which terminal emulator we have set as the default.
|
||||
ProcessResult result = await Process.run('which', ['x-terminal-emulator']);
|
||||
if (result.exitCode == 0) {
|
||||
String terminalEmulator = await File(result.stdout.toString().trim()).resolveSymbolicLinks();
|
||||
String terminalEmulator =
|
||||
await File(result.stdout.toString().trim()).resolveSymbolicLinks();
|
||||
terminalEmulator = path.basenameWithoutExtension(terminalEmulator);
|
||||
if (_supportedTerminalEmulators.contains(terminalEmulator)) {
|
||||
setState(() {
|
||||
|
|
@ -130,7 +132,8 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
|
|||
List<String> currentVms = [];
|
||||
Map<String, VmInfo> activeVms = {};
|
||||
|
||||
await for (var entity in Directory.current.list(recursive: false, followLinks: true)) {
|
||||
await for (var entity
|
||||
in Directory.current.list(recursive: false, followLinks: true)) {
|
||||
if ((entity.path.endsWith('.conf')) && (_isValidConf(entity.path))) {
|
||||
String name = path.basenameWithoutExtension(entity.path);
|
||||
currentVms.add(name);
|
||||
|
|
@ -169,7 +172,9 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
|
|||
|
||||
Widget _buildVmList() {
|
||||
List<Widget> _widgetList = [];
|
||||
final Color buttonColor = Theme.of(context).brightness == Brightness.dark ? Colors.white70 : Theme.of(context).colorScheme.primary;
|
||||
final Color buttonColor = Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.white70
|
||||
: Theme.of(context).colorScheme.primary;
|
||||
_widgetList.add(
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
|
@ -181,7 +186,8 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
|
|||
width: 8,
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(primary: Theme.of(context).canvasColor, onPrimary: buttonColor),
|
||||
style: ElevatedButton.styleFrom(
|
||||
primary: Theme.of(context).canvasColor, onPrimary: buttonColor),
|
||||
onPressed: () async {
|
||||
String? result = await FilePicker.platform.getDirectoryPath();
|
||||
if (result != null) {
|
||||
|
|
@ -276,7 +282,9 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
|
|||
context: context,
|
||||
builder: (BuildContext context) => AlertDialog(
|
||||
title: Text(context.t('Stop The Virtual Machine?')),
|
||||
content: Text(context.t('You are about to terminate the virtual machine', args: [currentVm])),
|
||||
content: Text(context.t(
|
||||
'You are about to terminate the virtual machine',
|
||||
args: [currentVm])),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
|
|
@ -300,44 +308,49 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
|
|||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.delete,
|
||||
color: active ? null : buttonColor,
|
||||
semanticLabel: 'Delete'
|
||||
),
|
||||
onPressed: active ? null : () {
|
||||
showDialog<String?>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => AlertDialog(
|
||||
title: Text('Delete ' + currentVm),
|
||||
content: Text(
|
||||
'You are about to delete ' + currentVm + '. This cannot be undone. ' +
|
||||
'Would you like to delete the disk image but keep the ' +
|
||||
'configuration, or delete the whole VM?'
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text('Cancel'),
|
||||
onPressed: () => Navigator.pop(context, 'cancel'),
|
||||
),
|
||||
TextButton(
|
||||
child: Text('Delete disk image'),
|
||||
onPressed: () => Navigator.pop(context, 'disk'),
|
||||
),
|
||||
TextButton(
|
||||
child: Text('Delete whole VM'),
|
||||
onPressed: () => Navigator.pop(context, 'vm'),
|
||||
) // set up the AlertDialog
|
||||
],
|
||||
),
|
||||
).then((result) async {
|
||||
result = result ?? 'cancel';
|
||||
if (result != 'cancel') {
|
||||
List<String> args = ['--vm', currentVm + '.conf', '--delete-' + result];
|
||||
await Process.start('quickemu', args);
|
||||
}
|
||||
});
|
||||
},
|
||||
icon: Icon(Icons.delete,
|
||||
color: active ? null : buttonColor,
|
||||
semanticLabel: 'Delete'),
|
||||
onPressed: active
|
||||
? null
|
||||
: () {
|
||||
showDialog<String?>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => AlertDialog(
|
||||
title: Text('Delete ' + currentVm),
|
||||
content: Text('You are about to delete ' +
|
||||
currentVm +
|
||||
'. This cannot be undone. ' +
|
||||
'Would you like to delete the disk image but keep the ' +
|
||||
'configuration, or delete the whole VM?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: const Text('Cancel'),
|
||||
onPressed: () =>
|
||||
Navigator.pop(context, 'cancel'),
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('Delete disk image'),
|
||||
onPressed: () => Navigator.pop(context, 'disk'),
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('Delete whole VM'),
|
||||
onPressed: () => Navigator.pop(context, 'vm'),
|
||||
) // set up the AlertDialog
|
||||
],
|
||||
),
|
||||
).then((result) async {
|
||||
result = result ?? 'cancel';
|
||||
if (result != 'cancel') {
|
||||
List<String> args = [
|
||||
'--vm',
|
||||
currentVm + '.conf',
|
||||
'--delete-' + result
|
||||
];
|
||||
await Process.start('quickemu', args);
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
)),
|
||||
|
|
@ -351,7 +364,9 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
|
|||
color: _spicy ? buttonColor : null,
|
||||
semanticLabel: 'Connect display with SPICE',
|
||||
),
|
||||
tooltip: _spicy ? 'Connect display with SPICE' : 'SPICE client not found',
|
||||
tooltip: _spicy
|
||||
? 'Connect display with SPICE'
|
||||
: 'SPICE client not found',
|
||||
onPressed: !_spicy
|
||||
? null
|
||||
: () {
|
||||
|
|
@ -359,19 +374,25 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
|
|||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: SvgPicture.asset('assets/images/console.svg', semanticsLabel: 'Connect with SSH', color: sshy ? buttonColor : Colors.grey),
|
||||
tooltip: sshy ? 'Connect with SSH' : 'SSH server not detected on guest',
|
||||
icon: SvgPicture.asset('assets/images/console.svg',
|
||||
semanticsLabel: 'Connect with SSH',
|
||||
color: sshy ? buttonColor : Colors.grey),
|
||||
tooltip: sshy
|
||||
? 'Connect with SSH'
|
||||
: 'SSH server not detected on guest',
|
||||
onPressed: !sshy
|
||||
? null
|
||||
: () {
|
||||
TextEditingController _usernameController = TextEditingController();
|
||||
TextEditingController _usernameController =
|
||||
TextEditingController();
|
||||
showDialog<bool>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => AlertDialog(
|
||||
title: Text('Launch SSH connection to $currentVm'),
|
||||
content: TextField(
|
||||
controller: _usernameController,
|
||||
decoration: const InputDecoration(hintText: "SSH username"),
|
||||
decoration: const InputDecoration(
|
||||
hintText: "SSH username"),
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
|
|
@ -387,10 +408,16 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
|
|||
).then((result) {
|
||||
result = result ?? false;
|
||||
if (result) {
|
||||
List<String> sshArgs = ['ssh', '-p', vmInfo.sshPort!, _usernameController.text + '@localhost'];
|
||||
List<String> sshArgs = [
|
||||
'ssh',
|
||||
'-p',
|
||||
vmInfo.sshPort!,
|
||||
_usernameController.text + '@localhost'
|
||||
];
|
||||
// Set the arguments to execute the ssh command in the default terminal.
|
||||
// Strip the extension as x-terminal-emulator may point to a .wrapper
|
||||
switch (path.basenameWithoutExtension(_terminalEmulator!)) {
|
||||
switch (path
|
||||
.basenameWithoutExtension(_terminalEmulator!)) {
|
||||
case 'gnome-terminal':
|
||||
case 'mate-terminal':
|
||||
sshArgs.insert(0, '--');
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:gettext_i18n/gettext_i18n.dart';
|
||||
|
||||
import '../model/operating_system.dart';
|
||||
|
|
@ -7,7 +8,8 @@ class OperatingSystemSelection extends StatefulWidget {
|
|||
const OperatingSystemSelection({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<OperatingSystemSelection> createState() => _OperatingSystemSelectionState();
|
||||
State<OperatingSystemSelection> createState() =>
|
||||
_OperatingSystemSelectionState();
|
||||
}
|
||||
|
||||
class _OperatingSystemSelectionState extends State<OperatingSystemSelection> {
|
||||
|
|
@ -22,7 +24,9 @@ class _OperatingSystemSelectionState extends State<OperatingSystemSelection> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var list = gOperatingSystems.where((os) => os.name.toLowerCase().contains(term.toLowerCase())).toList();
|
||||
var list = gOperatingSystems
|
||||
.where((os) => os.name.toLowerCase().contains(term.toLowerCase()))
|
||||
.toList();
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.t('Select operating system')),
|
||||
|
|
@ -44,7 +48,8 @@ class _OperatingSystemSelectionState extends State<OperatingSystemSelection> {
|
|||
Expanded(
|
||||
child: TextField(
|
||||
focusNode: focusNode,
|
||||
decoration: InputDecoration.collapsed(hintText: context.t('Search operating system')),
|
||||
decoration: InputDecoration.collapsed(
|
||||
hintText: context.t('Search operating system')),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
term = value;
|
||||
|
|
@ -69,9 +74,15 @@ class _OperatingSystemSelectionState extends State<OperatingSystemSelection> {
|
|||
itemCount: list.length,
|
||||
itemBuilder: (context, index) {
|
||||
var item = list[index];
|
||||
var icon = SvgPicture.asset(
|
||||
"assets/quickemu-icons/${item.code}-quickemu-white-pinkbg.svg",
|
||||
width: 32,
|
||||
height: 32,
|
||||
);
|
||||
return Card(
|
||||
child: ListTile(
|
||||
title: Text(item.name),
|
||||
leading: icon,
|
||||
onTap: () {
|
||||
Navigator.of(context).pop(item);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -24,7 +24,9 @@ class _OptionSelectionState extends State<OptionSelection> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var list = widget.version.options.where((e) => e.option.toLowerCase().contains(term.toLowerCase())).toList();
|
||||
var list = widget.version.options
|
||||
.where((e) => e.option.toLowerCase().contains(term.toLowerCase()))
|
||||
.toList();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
|
|
@ -49,7 +51,8 @@ class _OptionSelectionState extends State<OptionSelection> {
|
|||
Expanded(
|
||||
child: TextField(
|
||||
focusNode: focusNode,
|
||||
decoration: InputDecoration.collapsed(hintText: context.t('Search option')),
|
||||
decoration: InputDecoration.collapsed(
|
||||
hintText: context.t('Search option')),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
term = value;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ import '../model/version.dart';
|
|||
import 'option_selection.dart';
|
||||
|
||||
class VersionSelection extends StatefulWidget {
|
||||
const VersionSelection({Key? key, required this.operatingSystem}) : super(key: key);
|
||||
const VersionSelection({Key? key, required this.operatingSystem})
|
||||
: super(key: key);
|
||||
|
||||
final OperatingSystem operatingSystem;
|
||||
|
||||
|
|
@ -21,12 +22,14 @@ class _VersionSelectionState extends State<VersionSelection> {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.t('Select version for {0}', args: [widget.operatingSystem.name])),
|
||||
title: Text(context
|
||||
.t('Select version for {0}', args: [widget.operatingSystem.name])),
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
ListView.builder(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
shrinkWrap: true,
|
||||
itemCount: widget.operatingSystem.versions.length,
|
||||
itemBuilder: (context, index) {
|
||||
|
|
@ -35,17 +38,23 @@ class _VersionSelectionState extends State<VersionSelection> {
|
|||
child: ListTile(
|
||||
title: Text(item.version),
|
||||
onTap: () {
|
||||
if (widget.operatingSystem.versions[index].options.length > 1) {
|
||||
if (widget
|
||||
.operatingSystem.versions[index].options.length >
|
||||
1) {
|
||||
Navigator.of(context)
|
||||
.push<Option>(
|
||||
MaterialPageRoute(fullscreenDialog: true, builder: (context) => OptionSelection(widget.operatingSystem.versions[index])))
|
||||
.push<Option>(MaterialPageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (context) => OptionSelection(
|
||||
widget.operatingSystem.versions[index])))
|
||||
.then((selection) {
|
||||
if (selection != null) {
|
||||
Navigator.of(context).pop(Tuple2<Version, Option?>(item, selection));
|
||||
Navigator.of(context)
|
||||
.pop(Tuple2<Version, Option?>(item, selection));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Navigator.of(context).pop(Tuple2<Version, Option?>(item, widget.operatingSystem.versions[index].options[0]));
|
||||
Navigator.of(context).pop(Tuple2<Version, Option?>(item,
|
||||
widget.operatingSystem.versions[index].options[0]));
|
||||
}
|
||||
},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ final supportedLocales = [
|
|||
//'cy',
|
||||
'de',
|
||||
'en',
|
||||
'es',
|
||||
'fr',
|
||||
//'gd',
|
||||
'it',
|
||||
|
|
|
|||
|
|
@ -21,14 +21,18 @@ class CancelDismissButton extends StatelessWidget {
|
|||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
primary: Theme.of(context).colorScheme.surface,
|
||||
onPrimary: Theme.of(context).brightness == Brightness.dark ? Colors.white70 : Theme.of(context).colorScheme.primary,
|
||||
onPrimary: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.white70
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
onPressed: !downloadFinished
|
||||
? onCancel
|
||||
: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: downloadFinished ? Text(context.t('Dismiss')) : Text(context.t('Cancel')),
|
||||
child: downloadFinished
|
||||
? Text(context.t('Dismiss'))
|
||||
: Text(context.t('Cancel')),
|
||||
)
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,12 @@ import 'package:flutter/material.dart';
|
|||
import 'package:gettext_i18n/gettext_i18n.dart';
|
||||
|
||||
class DownloadLabel extends StatelessWidget {
|
||||
const DownloadLabel({Key? key, required this.downloadFinished, required this.data, required this.downloader}) : super(key: key);
|
||||
const DownloadLabel(
|
||||
{Key? key,
|
||||
required this.downloadFinished,
|
||||
required this.data,
|
||||
required this.downloader})
|
||||
: super(key: key);
|
||||
|
||||
final bool downloadFinished;
|
||||
final double? data;
|
||||
|
|
@ -16,8 +21,9 @@ class DownloadLabel extends StatelessWidget {
|
|||
? Text(context.t('Download finished.'))
|
||||
: data != null
|
||||
? downloader != 'zsync'
|
||||
? downloader == 'wget'
|
||||
? Text(context.t('Downloading...{0}%', args: [(data! * 100).toInt()]))
|
||||
? downloader == 'wget' || downloader == 'aria2c'
|
||||
? Text(context.t('Downloading... {0}%',
|
||||
args: [(data! * 100).toInt()]))
|
||||
: Text(context.t('{0} Mbs downloaded', args: [data!]))
|
||||
: Text(context.t("Downloading (no progress available)..."))
|
||||
: Text(context.t('Waiting for download to start')),
|
||||
|
|
|
|||
|
|
@ -26,14 +26,19 @@ class HomePageButton extends StatelessWidget {
|
|||
child: Center(
|
||||
child: Text(
|
||||
label?.toUpperCase() ?? '',
|
||||
style: Theme.of(context).textTheme.subtitle2?.copyWith(color: Colors.white),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.subtitle2
|
||||
?.copyWith(color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
primary: Theme.of(context).canvasColor,
|
||||
onPrimary: Theme.of(context).brightness == Brightness.dark ? Colors.white70 : Theme.of(context).colorScheme.primary,
|
||||
onPrimary: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.white70
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
onPressed: onPressed,
|
||||
child: Padding(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:gettext_i18n/gettext_i18n.dart';
|
||||
|
|
@ -27,7 +26,8 @@ class _HomePageButtonGroupState extends State<HomePageButtonGroup> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var _versionButtonLabel = _selectedVersion?.version ?? context.t('Select...');
|
||||
var _versionButtonLabel =
|
||||
_selectedVersion?.version ?? context.t('Select...');
|
||||
if (_selectedOption?.option.isNotEmpty ?? false) {
|
||||
_versionButtonLabel = "$_versionButtonLabel (${_selectedOption!.option})";
|
||||
}
|
||||
|
|
@ -38,12 +38,15 @@ class _HomePageButtonGroupState extends State<HomePageButtonGroup> {
|
|||
text: _selectedOperatingSystem?.name ?? context.t('Select...'),
|
||||
onPressed: () {
|
||||
Navigator.of(context)
|
||||
.push<OperatingSystem>(MaterialPageRoute(fullscreenDialog: true, builder: (context) => const OperatingSystemSelection()))
|
||||
.push<OperatingSystem>(MaterialPageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (context) => const OperatingSystemSelection()))
|
||||
.then((selection) {
|
||||
if (selection != null) {
|
||||
setState(() {
|
||||
_selectedOperatingSystem = selection;
|
||||
if (selection.versions.length == 1 && selection.versions.first.options.length == 1) {
|
||||
if (selection.versions.length == 1 &&
|
||||
selection.versions.first.options.length == 1) {
|
||||
_selectedVersion = selection.versions.first;
|
||||
_selectedOption = selection.versions.first.options.first;
|
||||
} else {
|
||||
|
|
@ -63,7 +66,8 @@ class _HomePageButtonGroupState extends State<HomePageButtonGroup> {
|
|||
Navigator.of(context)
|
||||
.push<Tuple2<Version, Option?>>(MaterialPageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (context) => VersionSelection(operatingSystem: _selectedOperatingSystem!),
|
||||
builder: (context) => VersionSelection(
|
||||
operatingSystem: _selectedOperatingSystem!),
|
||||
))
|
||||
.then((selection) {
|
||||
if (selection != null) {
|
||||
|
|
@ -115,14 +119,21 @@ class _HomePageButtonGroupState extends State<HomePageButtonGroup> {
|
|||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 32),
|
||||
child: Text(context.t('Downloading...'), style: Theme.of(context).textTheme.bodyText1?.copyWith(color: Colors.white)),
|
||||
child: Text(context.t('Downloading...'),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText1
|
||||
?.copyWith(color: Colors.white)),
|
||||
),
|
||||
const CircularProgressIndicator(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 32),
|
||||
child: Text(
|
||||
'Target : ${Directory.current.absolute.path}',
|
||||
style: Theme.of(context).textTheme.bodyText1?.copyWith(color: Colors.white),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText1
|
||||
?.copyWith(color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
@ -137,7 +148,8 @@ class _HomePageButtonGroupState extends State<HomePageButtonGroup> {
|
|||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
void showDoneDialog({required String operatingSystem, required String version}) {
|
||||
void showDoneDialog(
|
||||
{required String operatingSystem, required String version}) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
|
|
@ -155,10 +167,19 @@ class _HomePageButtonGroupState extends State<HomePageButtonGroup> {
|
|||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 32),
|
||||
child: Text(context.t('Done !'), style: Theme.of(context).textTheme.bodyText1?.copyWith(color: Colors.white)),
|
||||
child: Text(context.t('Done !'),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText1
|
||||
?.copyWith(color: Colors.white)),
|
||||
),
|
||||
Text(context.t('Now run {0} to start the VM', args: ["quickemu --vm $operatingSystem-$version"]),
|
||||
style: Theme.of(context).textTheme.bodyText1?.copyWith(color: Colors.white)),
|
||||
Text(
|
||||
context.t('Now run {0} to start the VM',
|
||||
args: ["quickemu --vm $operatingSystem-$version"]),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText1
|
||||
?.copyWith(color: Colors.white)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 32),
|
||||
child: ElevatedButton(
|
||||
|
|
@ -167,7 +188,10 @@ class _HomePageButtonGroupState extends State<HomePageButtonGroup> {
|
|||
},
|
||||
child: Text(
|
||||
'Dismiss',
|
||||
style: Theme.of(context).textTheme.bodyText1?.copyWith(color: Colors.white),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText1
|
||||
?.copyWith(color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ class MainMenu extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return Expanded(
|
||||
child: Container(
|
||||
color: Theme.of(context).brightness == Brightness.dark ? Theme.of(context).colorScheme.surface : Theme.of(context).colorScheme.primary,
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Theme.of(context).colorScheme.surface
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
|
|
@ -23,7 +25,8 @@ class MainMenu extends StatelessWidget {
|
|||
Navigator.of(context).push(
|
||||
PageRouteBuilder(
|
||||
fullscreenDialog: true,
|
||||
pageBuilder: (context, animation1, animation2) => const Manager(),
|
||||
pageBuilder: (context, animation1, animation2) =>
|
||||
const Manager(),
|
||||
transitionDuration: Duration.zero,
|
||||
),
|
||||
);
|
||||
|
|
@ -36,7 +39,8 @@ class MainMenu extends StatelessWidget {
|
|||
Navigator.of(context).push(
|
||||
PageRouteBuilder(
|
||||
fullscreenDialog: true,
|
||||
pageBuilder: (context, animation1, animation2) => const DownloaderPage(),
|
||||
pageBuilder: (context, animation1, animation2) =>
|
||||
const DownloaderPage(),
|
||||
transitionDuration: Duration.zero,
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 1.1.9
|
||||
version: 1.2.4
|
||||
|
||||
environment:
|
||||
sdk: ">=2.14.4 <3.0.0"
|
||||
|
|
@ -83,6 +83,7 @@ flutter:
|
|||
assets:
|
||||
- assets/images/
|
||||
- assets/i18n/
|
||||
- assets/quickemu-icons/
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware.
|
||||
|
|
|
|||
Loading…
Reference in New Issue