Support for `quickget list`, including options.

This commit is contained in:
Yannick Mauray 2021-10-20 01:30:11 +02:00
parent 0e082ac402
commit 5788e1fa9e
No known key found for this signature in database
GPG Key ID: 67C4AAC5E99CB909
8 changed files with 237 additions and 195 deletions

View File

@ -1,10 +1,10 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:quickgui/src/app.dart'; import 'package:quickgui/src/app.dart';
import 'package:quickgui/src/model/operating_system.dart'; import 'package:quickgui/src/model/operating_system.dart';
import 'package:quiver/iterables.dart'; import 'package:quickgui/src/model/version.dart';
import 'package:tuple/tuple.dart';
import 'package:window_size/window_size.dart'; import 'package:window_size/window_size.dart';
void main() async { void main() async {
@ -12,45 +12,32 @@ void main() async {
setWindowTitle('Quickgui : a flutter frontend for Quickget and Quickemu'); setWindowTitle('Quickgui : a flutter frontend for Quickget and Quickemu');
setWindowMinSize(const Size(692, 580)); setWindowMinSize(const Size(692, 580));
setWindowMaxSize(const Size(692, 580)); setWindowMaxSize(const Size(692, 580));
var config = await loadOperatingSystems(false); gOperatingSystems = await loadOperatingSystems(false);
runApp(const App()); runApp(const App());
} }
Future<List<OperatingSystem>> loadOperatingSystems(bool showUbuntus) async { Future<List<OperatingSystem>> loadOperatingSystems(bool showUbuntus) async {
var file = File('list.csv'); var process = await Process.run('quickget', ['list']);
var fileExists = file.existsSync();
if (fileExists) {
Stream<String> lines = file
.openRead()
.transform(utf8.decoder)
.transform(const LineSplitter())
.skip(1);
await for (var line in lines) {
print(line);
}
return [];
} else {
return await Process.run('quickget', [])
.then<List<OperatingSystem>>((process) {
var stdout = process.stdout as String; var stdout = process.stdout as String;
var codes = stdout.split('\n')[1].split(' ').where((element) => var output = <OperatingSystem>[];
showUbuntus ? element.contains('buntu') : !element.contains('buntu'));
var names = codes.map((code) => code
.toLowerCase()
.split('-')
.map((e) => e[0].toUpperCase() + e.substring(1))
.join(' '));
List<OperatingSystem> items = [];
if (!showUbuntus) {
items.add(OperatingSystem(name: 'Ubuntu', hasMore: true));
}
items.addAll(zip([codes, names])
.map((item) => OperatingSystem(code: item[0], name: item[1]))
.toList());
items.sort((a, b) => a.name.compareTo(b.name));
return items; OperatingSystem? currentOperatingSystem;
Version? currentVersion;
stdout.split('\n').skip(1).where((element) => element.isNotEmpty).map((e) => e.trim()).forEach((element) {
var supportedVersion = Tuple5.fromList(element.split(","));
if (currentOperatingSystem?.code != supportedVersion.item2) {
currentOperatingSystem = OperatingSystem(supportedVersion.item1, supportedVersion.item2);
output.add(currentOperatingSystem!);
currentVersion = null;
}
if (currentVersion?.version != supportedVersion.item3) {
currentVersion = Version(supportedVersion.item3);
currentOperatingSystem!.versions.add(currentVersion!);
}
currentVersion!.options.add(supportedVersion.item4);
}); });
}
return output;
} }

View File

@ -1,7 +1,11 @@
import 'package:quickgui/src/model/version.dart';
class OperatingSystem { class OperatingSystem {
OperatingSystem({required this.name, this.code, this.hasMore = false}); OperatingSystem(this.name, this.code) : versions = [];
final String name; final String name;
final String? code; final String code;
final bool hasMore; List<Version> versions;
} }
var gOperatingSystems = <OperatingSystem>[];

View File

@ -1,6 +1,6 @@
class Version { class Version {
Version({required this.name, this.code}); Version(this.version) : options = [];
final String name; final String version;
final String? code; final List<String> options;
} }

View File

@ -1,69 +1,77 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:quickgui/src/model/operating_system.dart'; import 'package:quickgui/src/model/operating_system.dart';
import 'package:quiver/iterables.dart';
class OperatingSystemSelection extends StatefulWidget { class OperatingSystemSelection extends StatefulWidget {
const OperatingSystemSelection({Key? key, this.showUbuntus = false}) const OperatingSystemSelection({Key? key}) : super(key: key);
: super(key: key);
final bool showUbuntus;
@override @override
State<OperatingSystemSelection> createState() => State<OperatingSystemSelection> createState() => _OperatingSystemSelectionState();
_OperatingSystemSelectionState();
} }
class _OperatingSystemSelectionState extends State<OperatingSystemSelection> { class _OperatingSystemSelectionState extends State<OperatingSystemSelection> {
late Future<List<OperatingSystem>> _future; var term = "";
final focusNode = FocusNode();
@override @override
void initState() { void initState() {
focusNode.requestFocus();
super.initState(); super.initState();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var list = gOperatingSystems.where((os) => os.name.toLowerCase().contains(term.toLowerCase())).toList();
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Select operating system'), title: const Text('Select operating system'),
bottom: PreferredSize(
preferredSize: const Size.fromHeight(kToolbarHeight),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).canvasColor,
), ),
body: FutureBuilder<List<OperatingSystem>>( child: Padding(
future: _future, padding: const EdgeInsets.all(8),
builder: (context, snapshot) { child: Material(
if (snapshot.hasData) { child: Row(
return SingleChildScrollView( mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Icon(Icons.search),
Expanded(
child: TextField(
focusNode: focusNode,
decoration: const InputDecoration.collapsed(hintText: 'Search operating system'),
onChanged: (value) {
setState(() {
term = value;
});
},
),
),
],
),
),
),
),
),
),
),
body: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
ListView.builder( ListView.builder(
padding: const EdgeInsets.only(top: 4),
shrinkWrap: true, shrinkWrap: true,
itemCount: snapshot.data!.length, itemCount: list.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
var item = snapshot.data![index]; var item = list[index];
return Card( return Card(
child: ListTile( child: ListTile(
title: Text(item.name), title: Text(item.name),
trailing: item.hasMore
? const Icon(Icons.chevron_right)
: null,
onTap: () { onTap: () {
if (!item.hasMore) {
Navigator.of(context).pop(item); Navigator.of(context).pop(item);
} else {
Navigator.of(context)
.push(MaterialPageRoute(
fullscreenDialog: true,
builder: (context) =>
const OperatingSystemSelection(
showUbuntus: true,
)))
.then((selection) {
if (selection != null) {
Navigator.of(context).pop(selection);
}
});
}
}, },
), ),
); );
@ -71,13 +79,6 @@ class _OperatingSystemSelectionState extends State<OperatingSystemSelection> {
), ),
], ],
), ),
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
), ),
); );
} }

View File

@ -0,0 +1,89 @@
import 'package:flutter/material.dart';
import 'package:quickgui/src/model/version.dart';
class OptionSelection extends StatefulWidget {
const OptionSelection(this.version, {Key? key}) : super(key: key);
final Version version;
@override
State<OptionSelection> createState() => _OptionSelectionState();
}
class _OptionSelectionState extends State<OptionSelection> {
var term = "";
final focusNode = FocusNode();
@override
void initState() {
focusNode.requestFocus();
super.initState();
}
@override
Widget build(BuildContext context) {
var list = widget.version.options.where((e) => e.toLowerCase().contains(term.toLowerCase())).toList();
return Scaffold(
appBar: AppBar(
title: const Text('Select option'),
bottom: widget.version.options.length <= 6
? null
: PreferredSize(
preferredSize: const Size.fromHeight(kToolbarHeight),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).canvasColor,
),
child: Padding(
padding: const EdgeInsets.all(8),
child: Material(
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Icon(Icons.search),
Expanded(
child: TextField(
focusNode: focusNode,
decoration: const InputDecoration.collapsed(hintText: 'Search option'),
onChanged: (value) {
setState(() {
term = value;
});
},
),
),
],
),
),
),
),
),
),
),
body: SingleChildScrollView(
child: Column(
children: [
ListView.builder(
shrinkWrap: true,
itemCount: list.length,
itemBuilder: (context, index) {
var item = list[index];
return Card(
child: ListTile(
title: Text(item),
onTap: () {
Navigator.of(context).pop(item);
},
),
);
},
),
],
),
),
);
}
}

View File

@ -1,9 +1,8 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:quickgui/src/model/operating_system.dart'; import 'package:quickgui/src/model/operating_system.dart';
import 'package:quickgui/src/model/version.dart'; import 'package:quickgui/src/model/version.dart';
import 'package:quiver/iterables.dart'; import 'package:quickgui/src/pages/option_selection.dart';
import 'package:tuple/tuple.dart';
class VersionSelection extends StatefulWidget { class VersionSelection extends StatefulWidget {
const VersionSelection({Key? key, required this.operatingSystem}) : super(key: key); const VersionSelection({Key? key, required this.operatingSystem}) : super(key: key);
@ -15,37 +14,36 @@ class VersionSelection extends StatefulWidget {
} }
class _VersionSelectionState extends State<VersionSelection> { class _VersionSelectionState extends State<VersionSelection> {
late Future<List<Version>> _future;
@override
void initState() {
_future = loadOperatingSystemVersions();
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text('Select version for ${widget.operatingSystem.name}'), title: Text('Select version for ${widget.operatingSystem.name}'),
), ),
body: FutureBuilder<List<Version>>( body: SingleChildScrollView(
future: _future,
builder: (context, snapshot) {
if (snapshot.hasData) {
return SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
ListView.builder( ListView.builder(
shrinkWrap: true, shrinkWrap: true,
itemCount: snapshot.data!.length, itemCount: widget.operatingSystem.versions.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
var item = snapshot.data![index]; var item = widget.operatingSystem.versions[index];
return Card( return Card(
child: ListTile( child: ListTile(
title: Text(item.name), title: Text(item.version),
onTap: () { onTap: () {
Navigator.of(context).pop(item); if (widget.operatingSystem.versions[index].options.length > 1) {
Navigator.of(context)
.push<String>(
MaterialPageRoute(fullscreenDialog: true, builder: (context) => OptionSelection(widget.operatingSystem.versions[index])))
.then((selection) {
if (selection != null) {
Navigator.of(context).pop(Tuple2<Version, String?>(item, selection));
}
});
} else {
Navigator.of(context).pop(Tuple2<Version, String?>(item, null));
}
}, },
), ),
); );
@ -53,27 +51,7 @@ class _VersionSelectionState extends State<VersionSelection> {
), ),
], ],
), ),
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
), ),
); );
} }
Future<List<Version>> loadOperatingSystemVersions() async {
return Process.run('quickget', [widget.operatingSystem.code!, 'dummy']).then<List<Version>>((process) {
var stdout = process.stdout as String;
var versions = stdout.split('\n')[1].split(' ');
var names =
versions.map((version) => version.toLowerCase().split('-').map((e) => e[0].toUpperCase() + e.substring(1)).join(' ').split('_').join('.')).toList();
List<Version> items = [];
items.addAll(zip([versions, names]).map((e) => Version(code: e[0], name: e[1])));
return items;
});
}
} }

View File

@ -7,6 +7,7 @@ import 'package:quickgui/src/model/version.dart';
import 'package:quickgui/src/pages/operating_system_selection.dart'; import 'package:quickgui/src/pages/operating_system_selection.dart';
import 'package:quickgui/src/pages/version_selection.dart'; import 'package:quickgui/src/pages/version_selection.dart';
import 'package:quickgui/src/widgets/home_page_button.dart'; import 'package:quickgui/src/widgets/home_page_button.dart';
import 'package:tuple/tuple.dart';
class HomePageButtons extends StatefulWidget { class HomePageButtons extends StatefulWidget {
const HomePageButtons({Key? key}) : super(key: key); const HomePageButtons({Key? key}) : super(key: key);
@ -16,10 +17,16 @@ class HomePageButtons extends StatefulWidget {
} }
class _HomePageButtonsState extends State<HomePageButtons> { class _HomePageButtonsState extends State<HomePageButtons> {
@override
Widget build(BuildContext context) {
OperatingSystem? _selectedOperatingSystem; OperatingSystem? _selectedOperatingSystem;
Version? _selectedVersion; Version? _selectedVersion;
String? _selectedOption;
@override
Widget build(BuildContext context) {
var _versionButtonLabel = _selectedVersion?.version ?? 'Select...';
if (_selectedOption != null) {
_versionButtonLabel = "$_versionButtonLabel ($_selectedOption)";
}
return Row( return Row(
children: [ children: [
HomePageButton( HomePageButton(
@ -27,14 +34,13 @@ class _HomePageButtonsState extends State<HomePageButtons> {
text: _selectedOperatingSystem?.name ?? 'Select...', text: _selectedOperatingSystem?.name ?? 'Select...',
onPressed: () { onPressed: () {
Navigator.of(context) Navigator.of(context)
.push<OperatingSystem>(MaterialPageRoute( .push<OperatingSystem>(MaterialPageRoute(fullscreenDialog: true, builder: (context) => const OperatingSystemSelection()))
fullscreenDialog: true,
builder: (context) => const OperatingSystemSelection()))
.then((selection) { .then((selection) {
if (selection != null) { if (selection != null) {
setState(() { setState(() {
_selectedOperatingSystem = selection; _selectedOperatingSystem = selection;
_selectedVersion = null; _selectedVersion = null;
_selectedOption = null;
}); });
} }
}); });
@ -42,19 +48,19 @@ class _HomePageButtonsState extends State<HomePageButtons> {
), ),
HomePageButton( HomePageButton(
label: "Version", label: "Version",
text: _selectedVersion?.name ?? 'Select...', text: _versionButtonLabel, //_selectedVersion?.version ?? 'Select...',
onPressed: (_selectedOperatingSystem != null) onPressed: (_selectedOperatingSystem != null)
? () { ? () {
Navigator.of(context) Navigator.of(context)
.push<Version>(MaterialPageRoute( .push<Tuple2<Version, String?>>(MaterialPageRoute(
fullscreenDialog: true, fullscreenDialog: true,
builder: (context) => VersionSelection( builder: (context) => VersionSelection(operatingSystem: _selectedOperatingSystem!),
operatingSystem: _selectedOperatingSystem!),
)) ))
.then((selection) { .then((selection) {
if (selection != null) { if (selection != null) {
setState(() { setState(() {
_selectedVersion = selection; _selectedVersion = selection.item1;
_selectedOption = selection.item2;
}); });
} }
}); });
@ -68,14 +74,9 @@ class _HomePageButtonsState extends State<HomePageButtons> {
? null ? null
: () async { : () async {
showLoadingIndicator(text: 'Downloading'); showLoadingIndicator(text: 'Downloading');
await Process.run('quickget', [ await Process.run('quickget', [_selectedOperatingSystem!.code, _selectedVersion!.version]);
_selectedOperatingSystem!.code!,
_selectedVersion!.code!
]);
hideLoadingIndicator(); hideLoadingIndicator();
showDoneDialog( showDoneDialog(operatingSystem: _selectedOperatingSystem!.code, version: _selectedVersion!.version);
operatingSystem: _selectedOperatingSystem!.code!,
version: _selectedVersion!.code!);
}, },
), ),
], ],
@ -101,21 +102,14 @@ class _HomePageButtonsState extends State<HomePageButtons> {
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 32), padding: const EdgeInsets.symmetric(vertical: 32),
child: Text('Downloading...', child: Text('Downloading...', style: Theme.of(context).textTheme.bodyText1?.copyWith(color: Colors.white)),
style: Theme.of(context)
.textTheme
.bodyText1
?.copyWith(color: Colors.white)),
), ),
const CircularProgressIndicator(), const CircularProgressIndicator(),
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 32), padding: const EdgeInsets.symmetric(vertical: 32),
child: Text( child: Text(
'Target : ${Directory.current.absolute.path}', 'Target : ${Directory.current.absolute.path}',
style: Theme.of(context) style: Theme.of(context).textTheme.bodyText1?.copyWith(color: Colors.white),
.textTheme
.bodyText1
?.copyWith(color: Colors.white),
), ),
), ),
], ],
@ -130,8 +124,7 @@ class _HomePageButtonsState extends State<HomePageButtons> {
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
void showDoneDialog( void showDoneDialog({required String operatingSystem, required String version}) {
{required String operatingSystem, required String version}) {
showDialog( showDialog(
context: context, context: context,
barrierDismissible: false, barrierDismissible: false,
@ -149,18 +142,10 @@ class _HomePageButtonsState extends State<HomePageButtons> {
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 32), padding: const EdgeInsets.symmetric(vertical: 32),
child: Text('Done !', child: Text('Done !', style: Theme.of(context).textTheme.bodyText1?.copyWith(color: Colors.white)),
style: Theme.of(context)
.textTheme
.bodyText1
?.copyWith(color: Colors.white)),
), ),
Text( Text('Now run "quickemu --vm $operatingSystem-$version" to start the VM',
'Now run "quickemu --vm $operatingSystem-$version" to start the VM', style: Theme.of(context).textTheme.bodyText1?.copyWith(color: Colors.white)),
style: Theme.of(context)
.textTheme
.bodyText1
?.copyWith(color: Colors.white)),
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 32), padding: const EdgeInsets.symmetric(vertical: 32),
child: ElevatedButton( child: ElevatedButton(
@ -169,10 +154,7 @@ class _HomePageButtonsState extends State<HomePageButtons> {
}, },
child: Text( child: Text(
'Dismiss', 'Dismiss',
style: Theme.of(context) style: Theme.of(context).textTheme.bodyText1?.copyWith(color: Colors.white),
.textTheme
.bodyText1
?.copyWith(color: Colors.white),
), ),
), ),
), ),

View File

@ -39,6 +39,7 @@ dependencies:
url: git://github.com/google/flutter-desktop-embedding.git url: git://github.com/google/flutter-desktop-embedding.git
path: plugins/window_size path: plugins/window_size
quiver: ^3.0.1+1 quiver: ^3.0.1+1
tuple: ^2.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: