feat: replace all material widgets with platform specific widgets
This commit is contained in:
parent
e9cde261b9
commit
3244f42d18
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
|
|
|||
138
lib/src/app.dart
138
lib/src/app.dart
|
|
@ -1,8 +1,10 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:fluent_ui/fluent_ui.dart' as fluent;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:gettext_i18n/gettext_i18n.dart';
|
||||
import 'package:platform_ui/platform_ui.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:quickgui/src/mixins/app_version.dart';
|
||||
import 'package:quickgui/src/pages/deget_not_found_page.dart';
|
||||
|
|
@ -24,6 +26,7 @@ class App extends StatefulWidget {
|
|||
class _AppState extends State<App> with PreferencesMixin {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
platform = TargetPlatform.macOS;
|
||||
return FutureBuilder<SharedPreferences>(
|
||||
future: SharedPreferences.getInstance(),
|
||||
builder: (context, snapshot) {
|
||||
|
|
@ -33,47 +36,59 @@ class _AppState extends State<App> with PreferencesMixin {
|
|||
appSettings.setActiveLocaleSilently(
|
||||
snapshot.data?.getString(prefCurrentLocale) ??
|
||||
Platform.localeName);
|
||||
var pref = snapshot.data!.getBool(prefThemeMode);
|
||||
var pref = snapshot.data!.getString(prefThemeMode);
|
||||
if (pref != null) {
|
||||
appSettings.useDarkModeSilently = pref;
|
||||
appSettings.themeModeSilently =
|
||||
ThemeMode.values.firstWhere((element) => element.name == pref);
|
||||
}
|
||||
return Consumer<AppSettings>(
|
||||
builder: (context, appSettings, _) => MaterialApp(
|
||||
theme: ThemeData(primarySwatch: Colors.pink),
|
||||
darkTheme: ThemeData.dark(),
|
||||
themeMode: appSettings.themeMode,
|
||||
home: AppVersion.packageInfo == null
|
||||
? const DebgetNotFoundPage()
|
||||
: const MainPage(),
|
||||
supportedLocales: supportedLocales.map((s) => s.contains("_")
|
||||
? Locale(s.split("_")[0], s.split("_")[1])
|
||||
: Locale(s)),
|
||||
localizationsDelegates: [
|
||||
GettextLocalizationsDelegate(),
|
||||
...GlobalMaterialLocalizations.delegates,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
],
|
||||
locale:
|
||||
Locale(appSettings.languageCode!, appSettings.countryCode),
|
||||
localeListResolutionCallback: (locales, supportedLocales) {
|
||||
if (locales != null) {
|
||||
for (var locale in locales) {
|
||||
var supportedLocale = supportedLocales.where((element) =>
|
||||
element.languageCode == locale.languageCode &&
|
||||
element.countryCode == locale.countryCode);
|
||||
if (supportedLocale.isNotEmpty) {
|
||||
return supportedLocale.first;
|
||||
}
|
||||
supportedLocale = supportedLocales.where((element) =>
|
||||
element.languageCode == locale.languageCode);
|
||||
if (supportedLocale.isNotEmpty) {
|
||||
return supportedLocale.first;
|
||||
builder: (context, appSettings, _) {
|
||||
return PlatformApp(
|
||||
themeMode: appSettings.themeMode,
|
||||
home: AppVersion.packageInfo == null
|
||||
? const DebgetNotFoundPage()
|
||||
: const MainPage(),
|
||||
supportedLocales: supportedLocales.map((s) => s.contains("_")
|
||||
? Locale(s.split("_")[0], s.split("_")[1])
|
||||
: Locale(s)),
|
||||
localizationsDelegates: [
|
||||
GettextLocalizationsDelegate(),
|
||||
...GlobalMaterialLocalizations.delegates,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
],
|
||||
windowButtonConfig: PlatformWindowButtonConfig(
|
||||
isMaximized: () => false,
|
||||
onClose: () {},
|
||||
onMinimize: () {},
|
||||
onMaximize: () {},
|
||||
onRestore: () {},
|
||||
),
|
||||
windowsTheme: fluent.ThemeData(
|
||||
scaffoldBackgroundColor: fluent.Colors.grey[50],
|
||||
),
|
||||
windowsDarkTheme: fluent.ThemeData.dark(),
|
||||
locale:
|
||||
Locale(appSettings.languageCode!, appSettings.countryCode),
|
||||
localeListResolutionCallback: (locales, supportedLocales) {
|
||||
if (locales != null) {
|
||||
for (var locale in locales) {
|
||||
var supportedLocale = supportedLocales.where((element) =>
|
||||
element.languageCode == locale.languageCode &&
|
||||
element.countryCode == locale.countryCode);
|
||||
if (supportedLocale.isNotEmpty) {
|
||||
return supportedLocale.first;
|
||||
}
|
||||
supportedLocale = supportedLocales.where((element) =>
|
||||
element.languageCode == locale.languageCode);
|
||||
if (supportedLocale.isNotEmpty) {
|
||||
return supportedLocale.first;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
return null;
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return const Center(
|
||||
|
|
@ -82,56 +97,5 @@ class _AppState extends State<App> with PreferencesMixin {
|
|||
}
|
||||
},
|
||||
);
|
||||
/*
|
||||
return FutureBuilder<bool?>(
|
||||
future: getPreference<bool>(prefThemeMode),
|
||||
builder: (context, AsyncSnapshot<bool?> snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.data != null) {
|
||||
context.read<AppSettings>().useDarkModeSilently = snapshot.data!;
|
||||
}
|
||||
return Consumer<AppSettings>(
|
||||
builder: (context, appTheme, _) => MaterialApp(
|
||||
theme: ThemeData(primarySwatch: Colors.pink),
|
||||
darkTheme: ThemeData.dark(),
|
||||
themeMode: appTheme.themeMode,
|
||||
home: const MainPage(),
|
||||
supportedLocales: supportedLocales.map((s) => s.contains("_")
|
||||
? Locale(s.split("_")[0], s.split("_")[1])
|
||||
: Locale(s)),
|
||||
localizationsDelegates: [
|
||||
GettextLocalizationsDelegate(),
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
],
|
||||
locale: const Locale('ru'),
|
||||
localeListResolutionCallback: (locales, supportedLocales) {
|
||||
if (locales != null) {
|
||||
for (var locale in locales) {
|
||||
var supportedLocale = supportedLocales.where((element) =>
|
||||
element.languageCode == locale.languageCode &&
|
||||
element.countryCode == locale.countryCode);
|
||||
if (supportedLocale.isNotEmpty) {
|
||||
return supportedLocale.first;
|
||||
}
|
||||
supportedLocale = supportedLocales.where((element) =>
|
||||
element.languageCode == locale.languageCode);
|
||||
if (supportedLocale.isNotEmpty) {
|
||||
return supportedLocale.first;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,12 +37,12 @@ class AppSettings extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
set useDarkModeSilently(bool useDarkMode) {
|
||||
_themeMode = useDarkMode ? ThemeMode.dark : ThemeMode.light;
|
||||
set themeModeSilently(ThemeMode themeMode) {
|
||||
_themeMode = themeMode;
|
||||
}
|
||||
|
||||
set useDarkMode(bool useDarkMode) {
|
||||
useDarkModeSilently = useDarkMode;
|
||||
set themeMode(ThemeMode themeMode) {
|
||||
_themeMode = themeMode;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:platform_ui/platform_ui.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class DebgetNotFoundPage extends StatelessWidget {
|
||||
|
|
@ -7,12 +8,19 @@ class DebgetNotFoundPage extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
return PlatformScaffold(
|
||||
appBar: PlatformAppBar(
|
||||
title: PlatformText.subheading('Quickgui'),
|
||||
actions: const [
|
||||
PlatformWindowButtons(),
|
||||
],
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text(
|
||||
const PlatformText(
|
||||
'quickemu was not found in your PATH',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
|
|
@ -21,7 +29,7 @@ class DebgetNotFoundPage extends StatelessWidget {
|
|||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
const Text(
|
||||
const PlatformText(
|
||||
'Please install it and try again.',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ import 'dart:io';
|
|||
import 'package:desktop_notifications/desktop_notifications.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gettext_i18n/gettext_i18n.dart';
|
||||
import 'package:platform_ui/platform_ui.dart';
|
||||
|
||||
import '../model/operating_system.dart';
|
||||
import '../model/option.dart';
|
||||
import '../model/version.dart';
|
||||
import '../widgets/downloader/cancel_dismiss_button.dart';
|
||||
import '../widgets/downloader/download_label.dart';
|
||||
import '../widgets/downloader/download_progress_bar.dart';
|
||||
|
||||
|
|
@ -129,9 +129,9 @@ class _DownloaderState extends State<Downloader> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
return PlatformScaffold(
|
||||
appBar: PlatformAppBar(
|
||||
title: PlatformText(
|
||||
context.t('Downloading {0}', args: [
|
||||
'${widget.operatingSystem.name} ${widget.version.version}' +
|
||||
(widget.option!.option.isNotEmpty
|
||||
|
|
@ -139,48 +139,70 @@ class _DownloaderState extends State<Downloader> {
|
|||
: '')
|
||||
]),
|
||||
),
|
||||
actions: const [
|
||||
PlatformWindowButtons(),
|
||||
],
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: StreamBuilder(
|
||||
stream: _progressStream,
|
||||
builder: (context, AsyncSnapshot<double> snapshot) {
|
||||
var data = !snapshot.hasData ||
|
||||
widget.option!.downloader != 'wget' ||
|
||||
widget.option!.downloader != 'aria2c'
|
||||
? null
|
||||
: snapshot.data;
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
DownloadLabel(
|
||||
downloadFinished: _downloadFinished,
|
||||
data: snapshot.hasData ? snapshot.data : null,
|
||||
downloader: widget.option!.downloader,
|
||||
),
|
||||
DownloadProgressBar(
|
||||
downloadFinished: _downloadFinished,
|
||||
data: snapshot.hasData ? data : null,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 32),
|
||||
child: Text(context.t('Target folder : {0}',
|
||||
args: [Directory.current.path])),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
body: Center(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: StreamBuilder(
|
||||
stream: _progressStream,
|
||||
builder: (context, AsyncSnapshot<double> snapshot) {
|
||||
var data = !snapshot.hasData ||
|
||||
widget.option!.downloader != 'wget' ||
|
||||
widget.option!.downloader != 'aria2c'
|
||||
? null
|
||||
: snapshot.data;
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
DownloadLabel(
|
||||
downloadFinished: _downloadFinished,
|
||||
data: snapshot.hasData ? snapshot.data : null,
|
||||
downloader: widget.option!.downloader,
|
||||
),
|
||||
DownloadProgressBar(
|
||||
downloadFinished: _downloadFinished,
|
||||
data: snapshot.hasData ? data : null,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 32),
|
||||
child: PlatformText(context.t('Target folder : {0}',
|
||||
args: [Directory.current.path])),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
CancelDismissButton(
|
||||
onCancel: () {
|
||||
_process?.kill();
|
||||
},
|
||||
downloadFinished: _downloadFinished,
|
||||
),
|
||||
],
|
||||
PlatformFilledButton(
|
||||
onPressed: () {
|
||||
if (!_downloadFinished) {
|
||||
_process?.kill();
|
||||
} else {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateProperty.all(Colors.red[400]),
|
||||
),
|
||||
child: _downloadFinished
|
||||
? PlatformText(
|
||||
context.t('Dismiss'),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
)
|
||||
: PlatformText(
|
||||
context.t('Cancel'),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:gettext_i18n/gettext_i18n.dart';
|
||||
import 'package:platform_ui/platform_ui.dart';
|
||||
|
||||
import '../widgets/home_page/downloader_menu.dart';
|
||||
import '../widgets/home_page/logo.dart';
|
||||
|
|
@ -9,9 +10,12 @@ class DownloaderPage extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.t('Downloader')),
|
||||
return PlatformScaffold(
|
||||
appBar: PlatformAppBar(
|
||||
title: PlatformText(context.t('Downloader')),
|
||||
actions: const [
|
||||
PlatformWindowButtons(),
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
children: const [
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:gettext_i18n/gettext_i18n.dart';
|
||||
import 'package:platform_ui/platform_ui.dart';
|
||||
import 'package:window_size/window_size.dart';
|
||||
|
||||
import '../widgets/home_page/logo.dart';
|
||||
import '../widgets/home_page/main_menu.dart';
|
||||
import '../widgets/left_menu.dart';
|
||||
import '../widgets/settings_page.dart';
|
||||
|
||||
class MainPage extends StatefulWidget {
|
||||
const MainPage({Key? key}) : super(key: key);
|
||||
|
|
@ -23,11 +24,26 @@ class _MainPageState extends State<MainPage> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.t('Main menu')),
|
||||
return PlatformScaffold(
|
||||
appBar: PlatformAppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
title: PlatformText.subheading(context.t('Quickgui')),
|
||||
centerTitle: true,
|
||||
actions: [
|
||||
PlatformIconButton(
|
||||
icon: const Icon(Icons.settings),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const SettingsPage(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const PlatformWindowButtons(),
|
||||
],
|
||||
),
|
||||
drawer: const LeftMenu(),
|
||||
body: Column(
|
||||
children: const [
|
||||
Logo(),
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ import 'dart:core';
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:gettext_i18n/gettext_i18n.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:platform_ui/platform_ui.dart';
|
||||
|
||||
import '../globals.dart';
|
||||
import '../mixins/preferences_mixin.dart';
|
||||
|
|
@ -173,9 +173,10 @@ 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 =
|
||||
PlatformTheme.of(context).brightness == Brightness.dark
|
||||
? Colors.white70
|
||||
: PlatformTheme.of(context).primaryColor!;
|
||||
_widgetList.addAll(
|
||||
[
|
||||
Padding(
|
||||
|
|
@ -183,32 +184,33 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
|
|||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
PlatformText(
|
||||
"${context.t('Directory where the machines are stored')}:",
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () async {
|
||||
String? result =
|
||||
await FilePicker.platform.getDirectoryPath();
|
||||
if (result != null) {
|
||||
setState(() {
|
||||
Directory.current = result;
|
||||
});
|
||||
|
||||
savePreference(
|
||||
prefWorkingDirectory, Directory.current.path);
|
||||
_getVms(context);
|
||||
}
|
||||
},
|
||||
text: Directory.current.path,
|
||||
style:
|
||||
TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||
PlatformFilledButton(
|
||||
child: Row(
|
||||
children: [
|
||||
PlatformText(Directory.current.path),
|
||||
const SizedBox(width: 8),
|
||||
const Icon(Icons.folder_outlined),
|
||||
],
|
||||
),
|
||||
isSecondary: true,
|
||||
onPressed: () async {
|
||||
String? result = await FilePicker.platform.getDirectoryPath();
|
||||
if (result != null) {
|
||||
setState(() {
|
||||
Directory.current = result;
|
||||
});
|
||||
|
||||
savePreference(
|
||||
prefWorkingDirectory, Directory.current.path);
|
||||
_getVms(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -257,12 +259,12 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
|
|||
}
|
||||
}
|
||||
return <Widget>[
|
||||
ListTile(
|
||||
title: Text(currentVm),
|
||||
PlatformListTile(
|
||||
title: PlatformText(currentVm),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
PlatformIconButton(
|
||||
icon: Icon(
|
||||
active ? Icons.play_arrow : Icons.play_arrow_outlined,
|
||||
color: active ? Colors.green : buttonColor,
|
||||
|
|
@ -283,7 +285,7 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
|
|||
_activeVms = activeVms;
|
||||
});
|
||||
}),
|
||||
IconButton(
|
||||
PlatformIconButton(
|
||||
icon: Icon(
|
||||
active ? Icons.stop : Icons.stop_outlined,
|
||||
color: active ? Colors.red : null,
|
||||
|
|
@ -292,21 +294,30 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
|
|||
onPressed: !active
|
||||
? null
|
||||
: () {
|
||||
showDialog<bool>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => AlertDialog(
|
||||
title: Text(context.t('Stop The Virtual Machine?')),
|
||||
content: Text(context.t(
|
||||
showPlatformAlertDialog<bool>(
|
||||
context,
|
||||
builder: (BuildContext context) =>
|
||||
PlatformAlertDialog(
|
||||
title: PlatformText(
|
||||
context.t('Stop The Virtual Machine?'),
|
||||
),
|
||||
content: PlatformText(
|
||||
context.t(
|
||||
'You are about to terminate the virtual machine',
|
||||
args: [currentVm])),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: Text(context.t('Cancel')),
|
||||
args: [currentVm],
|
||||
),
|
||||
TextButton(
|
||||
),
|
||||
primaryActions: [
|
||||
PlatformFilledButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(context.t('OK')),
|
||||
child: PlatformText(context.t('OK')),
|
||||
),
|
||||
],
|
||||
secondaryActions: [
|
||||
PlatformFilledButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
isSecondary: true,
|
||||
child: PlatformText(context.t('Cancel')),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -321,38 +332,80 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
|
|||
});
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.delete,
|
||||
color: active ? null : buttonColor,
|
||||
semanticLabel: 'Delete'),
|
||||
PlatformIconButton(
|
||||
icon: Icon(
|
||||
Icons.delete_forever_outlined,
|
||||
color: active
|
||||
? PlatformTheme.of(context).borderColor
|
||||
: Colors.red[400],
|
||||
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'),
|
||||
showPlatformAlertDialog<String?>(
|
||||
context,
|
||||
builder: (BuildContext context) {
|
||||
return PlatformAlertDialog(
|
||||
title: PlatformText('Delete ' + currentVm),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: PlatformText(
|
||||
'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?',
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
PlatformFilledButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor:
|
||||
MaterialStatePropertyAll(
|
||||
Colors.red[400]),
|
||||
),
|
||||
child: const PlatformText(
|
||||
'Delete disk image',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
onPressed: () =>
|
||||
Navigator.pop(context, 'disk'),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
PlatformFilledButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor:
|
||||
MaterialStatePropertyAll(
|
||||
Colors.red[400]),
|
||||
),
|
||||
child: const PlatformText(
|
||||
'Delete whole VM',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
onPressed: () =>
|
||||
Navigator.pop(context, 'vm'),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
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
|
||||
],
|
||||
),
|
||||
secondaryActions: [
|
||||
PlatformFilledButton(
|
||||
child: const PlatformText('Cancel'),
|
||||
isSecondary: true,
|
||||
onPressed: () =>
|
||||
Navigator.pop(context, 'cancel'),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
).then((result) async {
|
||||
result = result ?? 'cancel';
|
||||
if (result != 'cancel') {
|
||||
|
|
@ -369,10 +422,13 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
|
|||
],
|
||||
)),
|
||||
if (connectInfo.isNotEmpty)
|
||||
ListTile(
|
||||
title: Text(connectInfo, style: const TextStyle(fontSize: 12)),
|
||||
trailing: Row(mainAxisSize: MainAxisSize.min, children: <Widget>[
|
||||
IconButton(
|
||||
PlatformListTile(
|
||||
title:
|
||||
PlatformText(connectInfo, style: const TextStyle(fontSize: 12)),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
PlatformIconButton(
|
||||
icon: Icon(
|
||||
Icons.monitor,
|
||||
color: _spicy ? buttonColor : null,
|
||||
|
|
@ -387,7 +443,7 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
|
|||
Process.start('spicy', ['-p', vmInfo.spicePort!]);
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
PlatformIconButton(
|
||||
icon: SvgPicture.asset('assets/images/console.svg',
|
||||
semanticsLabel: 'Connect with SSH',
|
||||
color: sshy ? buttonColor : Colors.grey),
|
||||
|
|
@ -399,23 +455,23 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
|
|||
: () {
|
||||
TextEditingController _usernameController =
|
||||
TextEditingController();
|
||||
showDialog<bool>(
|
||||
context: context,
|
||||
showPlatformAlertDialog<bool>(
|
||||
context,
|
||||
builder: (BuildContext context) => AlertDialog(
|
||||
title: Text('Launch SSH connection to $currentVm'),
|
||||
content: TextField(
|
||||
title: PlatformText(
|
||||
'Launch SSH connection to $currentVm'),
|
||||
content: PlatformTextField(
|
||||
controller: _usernameController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: "SSH username"),
|
||||
placeholder: "SSH username",
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
PlatformTextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: const Text('Cancel'),
|
||||
child: const PlatformText('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
PlatformTextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: const Text('Connect'),
|
||||
child: const PlatformText('Connect'),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -463,16 +519,21 @@ class _ManagerState extends State<Manager> with PreferencesMixin {
|
|||
});
|
||||
},
|
||||
),
|
||||
])),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider()
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.t('Manager')),
|
||||
return PlatformScaffold(
|
||||
appBar: PlatformAppBar(
|
||||
title: PlatformText(context.t('Manager')),
|
||||
actions: const [
|
||||
PlatformWindowButtons(),
|
||||
],
|
||||
),
|
||||
body: _buildVmList(),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:gettext_i18n/gettext_i18n.dart';
|
||||
import 'package:platform_ui/platform_ui.dart';
|
||||
|
||||
import '../model/operating_system.dart';
|
||||
|
||||
|
|
@ -27,62 +28,49 @@ class _OperatingSystemSelectionState extends State<OperatingSystemSelection> {
|
|||
var list = gOperatingSystems
|
||||
.where((os) => os.name.toLowerCase().contains(term.toLowerCase()))
|
||||
.toList();
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.t('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,
|
||||
),
|
||||
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: InputDecoration.collapsed(
|
||||
hintText: context.t('Search operating system')),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
term = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
return PlatformScaffold(
|
||||
appBar: PlatformAppBar(
|
||||
title: PlatformText(context.t('Select operating system')),
|
||||
actions: const [
|
||||
PlatformWindowButtons(),
|
||||
],
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: PlatformTextField(
|
||||
focusNode: focusNode,
|
||||
placeholder: context.t('Search operating system'),
|
||||
prefixIcon: Icons.search,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
term = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
ListView.builder(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
shrinkWrap: true,
|
||||
itemCount: list.length,
|
||||
itemBuilder: (context, index) {
|
||||
var item = list[index];
|
||||
var icon = SvgPicture.asset(
|
||||
"assets/quickemu-icons/${item.code}.svg",
|
||||
width: 32,
|
||||
height: 32,
|
||||
);
|
||||
return Card(
|
||||
child: ListTile(
|
||||
title: Text(item.name),
|
||||
leading: icon,
|
||||
color: PlatformTheme.of(context).secondaryBackgroundColor,
|
||||
elevation: platform == TargetPlatform.macOS ? 0 : null,
|
||||
child: PlatformListTile(
|
||||
title: PlatformText(item.name),
|
||||
leading: SvgPicture.asset(
|
||||
"assets/quickemu-icons/${item.code}.svg",
|
||||
key: ValueKey(item.code),
|
||||
width: 32,
|
||||
height: 32,
|
||||
placeholderBuilder: (context) {
|
||||
return const Icon(Icons.computer_rounded);
|
||||
},
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.of(context).pop(item);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:gettext_i18n/gettext_i18n.dart';
|
||||
import 'package:platform_ui/platform_ui.dart';
|
||||
|
||||
import '../model/version.dart';
|
||||
|
||||
|
|
@ -30,7 +31,7 @@ class _OptionSelectionState extends State<OptionSelection> {
|
|||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.t('Select option')),
|
||||
title: PlatformText(context.t('Select option')),
|
||||
bottom: widget.version.options.length <= 6
|
||||
? null
|
||||
: PreferredSize(
|
||||
|
|
@ -77,8 +78,10 @@ class _OptionSelectionState extends State<OptionSelection> {
|
|||
itemBuilder: (context, index) {
|
||||
var item = list[index];
|
||||
return Card(
|
||||
color: PlatformTheme.of(context).secondaryBackgroundColor,
|
||||
elevation: platform == TargetPlatform.macOS ? 0 : null,
|
||||
child: ListTile(
|
||||
title: Text(item.option),
|
||||
title: PlatformText(item.option),
|
||||
onTap: () {
|
||||
Navigator.of(context).pop(item);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:gettext_i18n/gettext_i18n.dart';
|
||||
import 'package:platform_ui/platform_ui.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import '../model/operating_system.dart';
|
||||
|
|
@ -34,49 +35,32 @@ class _VersionSelectionState extends State<VersionSelection> {
|
|||
version.version.toLowerCase().contains(term.toLowerCase()))
|
||||
.toList();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context
|
||||
.t('Select version for {0}', args: [widget.operatingSystem.name])),
|
||||
bottom: 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: InputDecoration.collapsed(
|
||||
hintText: context.t('Search version'),
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
term = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
return PlatformScaffold(
|
||||
appBar: PlatformAppBar(
|
||||
title: PlatformText(
|
||||
context
|
||||
.t('Select version for {0}', args: [widget.operatingSystem.name]),
|
||||
),
|
||||
actions: const [
|
||||
PlatformWindowButtons(),
|
||||
],
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: PlatformTextField(
|
||||
focusNode: focusNode,
|
||||
prefixIcon: Icons.search,
|
||||
placeholder: context.t('Search version'),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
term = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
ListView.builder(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
shrinkWrap: true,
|
||||
|
|
@ -84,8 +68,8 @@ class _VersionSelectionState extends State<VersionSelection> {
|
|||
itemBuilder: (context, index) {
|
||||
var item = list[index];
|
||||
return Card(
|
||||
child: ListTile(
|
||||
title: Text(item.version),
|
||||
child: PlatformListTile(
|
||||
title: PlatformText(item.version),
|
||||
onTap: () {
|
||||
if (item.options.length > 1) {
|
||||
Navigator.of(context)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:gettext_i18n/gettext_i18n.dart';
|
||||
import 'package:platform_ui/platform_ui.dart';
|
||||
|
||||
class CancelDismissButton extends StatelessWidget {
|
||||
const CancelDismissButton({
|
||||
|
|
@ -20,7 +21,7 @@ class CancelDismissButton extends StatelessWidget {
|
|||
children: [
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
primary: Theme.of(context).colorScheme.surface,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
onPrimary: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.white70
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
|
|
@ -31,8 +32,8 @@ class CancelDismissButton extends StatelessWidget {
|
|||
Navigator.of(context).pop();
|
||||
},
|
||||
child: downloadFinished
|
||||
? Text(context.t('Dismiss'))
|
||||
: Text(context.t('Cancel')),
|
||||
? PlatformText(context.t('Dismiss'))
|
||||
: PlatformText(context.t('Cancel')),
|
||||
)
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:gettext_i18n/gettext_i18n.dart';
|
||||
import 'package:platform_ui/platform_ui.dart';
|
||||
|
||||
class DownloadLabel extends StatelessWidget {
|
||||
const DownloadLabel(
|
||||
|
|
@ -18,15 +19,17 @@ class DownloadLabel extends StatelessWidget {
|
|||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: downloadFinished
|
||||
? Text(context.t('Download finished.'))
|
||||
? PlatformText(context.t('Download finished.'))
|
||||
: data != null
|
||||
? downloader != 'zsync'
|
||||
? downloader == 'wget' || downloader == 'aria2c'
|
||||
? Text(context.t('Downloading... {0}%',
|
||||
? PlatformText(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')),
|
||||
: PlatformText(
|
||||
context.t('{0} Mbs downloaded', args: [data!]))
|
||||
: PlatformText(
|
||||
context.t("Downloading (no progress available)..."))
|
||||
: PlatformText(context.t('Waiting for download to start')),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gettext_i18n/gettext_i18n.dart';
|
||||
import 'package:platform_ui/platform_ui.dart';
|
||||
|
||||
import '../../globals.dart';
|
||||
import '../../mixins/preferences_mixin.dart';
|
||||
|
|
@ -30,65 +30,59 @@ class _DownloaderMenuState extends State<DownloaderMenu> with PreferencesMixin {
|
|||
@override
|
||||
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,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"${context.t('Directory where the machines are stored')}:",
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () async {
|
||||
var folder = await FilePicker.platform
|
||||
.getDirectoryPath(dialogTitle: "Pick a folder");
|
||||
if (folder != null) {
|
||||
setState(() {
|
||||
Directory.current = folder;
|
||||
});
|
||||
savePreference(
|
||||
prefWorkingDirectory, Directory.current.path);
|
||||
}
|
||||
},
|
||||
text: Directory.current.path,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(
|
||||
thickness: 2,
|
||||
),
|
||||
Row(
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: const [
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||
child: HomePageButtonGroup(),
|
||||
)
|
||||
PlatformText(
|
||||
"${context.t('Directory where the machines are stored')}:",
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
PlatformFilledButton(
|
||||
child: Row(
|
||||
children: [
|
||||
PlatformText(Directory.current.path),
|
||||
const SizedBox(width: 8),
|
||||
const Icon(Icons.folder_outlined),
|
||||
],
|
||||
),
|
||||
isSecondary: true,
|
||||
onPressed: () async {
|
||||
var folder = await FilePicker.platform
|
||||
.getDirectoryPath(dialogTitle: "Pick a folder");
|
||||
if (folder != null) {
|
||||
setState(() {
|
||||
Directory.current = folder;
|
||||
});
|
||||
savePreference(
|
||||
prefWorkingDirectory, Directory.current.path);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: const [
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||
child: HomePageButtonGroup(),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:platform_ui/platform_ui.dart';
|
||||
|
||||
class HomePageButton extends StatelessWidget {
|
||||
const HomePageButton({
|
||||
|
|
@ -16,38 +17,25 @@ class HomePageButton extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return Expanded(
|
||||
flex: 1,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 24, 12, 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 24),
|
||||
child: Center(
|
||||
child: Text(
|
||||
label?.toUpperCase() ?? '',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.subtitle2
|
||||
?.copyWith(color: Colors.white),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 24),
|
||||
child: Center(
|
||||
child: PlatformText.label(
|
||||
label?.toUpperCase() ?? '',
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
primary: Theme.of(context).canvasColor,
|
||||
onPrimary: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.white70
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
onPressed: onPressed,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(0, 16, 0, 16),
|
||||
child: Text(text),
|
||||
),
|
||||
),
|
||||
PlatformFilledButton(
|
||||
onPressed: onPressed,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(0, 16, 0, 16),
|
||||
child: PlatformText(text),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:gettext_i18n/gettext_i18n.dart';
|
||||
import 'package:platform_ui/platform_ui.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import '../../model/operating_system.dart';
|
||||
import '../../model/option.dart';
|
||||
|
|
@ -10,7 +12,79 @@ import '../../model/version.dart';
|
|||
import '../../pages/downloader.dart';
|
||||
import '../../pages/operating_system_selection.dart';
|
||||
import '../../pages/version_selection.dart';
|
||||
import '../home_page/home_page_button.dart';
|
||||
|
||||
class ProgressArrowPainter extends CustomPainter {
|
||||
final Color? oddColor;
|
||||
final Color? evenColor;
|
||||
final double radius;
|
||||
const ProgressArrowPainter({
|
||||
this.oddColor,
|
||||
this.evenColor,
|
||||
this.radius = 3,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
const icon = Icons.arrow_right_alt_rounded;
|
||||
final paint = Paint()
|
||||
..strokeWidth = 2
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
final spacing = (radius + 1) * 2;
|
||||
final balls = ((size.width / spacing) - 2).abs();
|
||||
|
||||
TextPainter textPainter = TextPainter(textDirection: TextDirection.ltr);
|
||||
textPainter.text = TextSpan(
|
||||
text: String.fromCharCode(icon.codePoint),
|
||||
style: TextStyle(
|
||||
color: balls % 2 != 0
|
||||
? evenColor ?? Colors.grey[500]!
|
||||
: oddColor ?? Colors.grey[600]!,
|
||||
fontSize: 30,
|
||||
fontFamily: icon.fontFamily,
|
||||
package:
|
||||
icon.fontPackage, // This line is mandatory for external icon packs
|
||||
),
|
||||
);
|
||||
textPainter.layout();
|
||||
textPainter.paint(
|
||||
canvas,
|
||||
Offset(spacing * (balls).abs(), size.height / 2 - 15),
|
||||
);
|
||||
|
||||
for (var i = 0; i < balls; i++) {
|
||||
if (i % 2 == 0) {
|
||||
paint.color = evenColor ?? Colors.grey[500]!;
|
||||
} else {
|
||||
paint.color = oddColor ?? Colors.grey[600]!;
|
||||
}
|
||||
canvas.drawCircle(
|
||||
Offset((size.height / 2) + i * spacing, size.height / 2),
|
||||
radius,
|
||||
paint,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class ProgressArrow extends StatelessWidget {
|
||||
const ProgressArrow({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12).copyWith(top: 30),
|
||||
child: const CustomPaint(
|
||||
painter: ProgressArrowPainter(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HomePageButtonGroup extends StatefulWidget {
|
||||
const HomePageButtonGroup({Key? key}) : super(key: key);
|
||||
|
|
@ -33,69 +107,127 @@ class _HomePageButtonGroupState extends State<HomePageButtonGroup> {
|
|||
}
|
||||
return Row(
|
||||
children: [
|
||||
HomePageButton(
|
||||
label: context.t("Operating system"),
|
||||
text: _selectedOperatingSystem?.name ?? context.t('Select...'),
|
||||
onPressed: () {
|
||||
Navigator.of(context)
|
||||
.push<OperatingSystem>(MaterialPageRoute(
|
||||
Column(
|
||||
children: [
|
||||
PlatformText(context.t("Operating system")),
|
||||
const SizedBox(height: 10),
|
||||
PlatformFilledButton(
|
||||
child: Row(
|
||||
children: [
|
||||
_selectedOperatingSystem != null
|
||||
? SvgPicture.asset(
|
||||
"assets/quickemu-icons/${_selectedOperatingSystem!.code}.svg",
|
||||
width: 32,
|
||||
height: 32,
|
||||
key: ValueKey(_selectedOperatingSystem?.code),
|
||||
placeholderBuilder: (context) {
|
||||
return const Icon(
|
||||
Icons.settings_applications_sharp);
|
||||
},
|
||||
)
|
||||
: const Icon(Icons.settings_applications_sharp),
|
||||
const SizedBox(width: 8),
|
||||
PlatformText(
|
||||
_selectedOperatingSystem == null
|
||||
? context.t('Select...')
|
||||
: _selectedOperatingSystem!.name,
|
||||
),
|
||||
],
|
||||
),
|
||||
onPressed: () async {
|
||||
final selection =
|
||||
await Navigator.of(context).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) {
|
||||
_selectedVersion = selection.versions.first;
|
||||
_selectedOption = selection.versions.first.options.first;
|
||||
} else {
|
||||
_selectedVersion = null;
|
||||
_selectedOption = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
HomePageButton(
|
||||
label: context.t('Version'),
|
||||
text: _versionButtonLabel, //_selectedVersion?.version ?? 'Select...',
|
||||
onPressed: (_selectedOperatingSystem != null)
|
||||
? () {
|
||||
Navigator.of(context)
|
||||
.push<Tuple2<Version, Option?>>(MaterialPageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (context) => VersionSelection(
|
||||
operatingSystem: _selectedOperatingSystem!),
|
||||
))
|
||||
.then((selection) {
|
||||
if (selection != null) {
|
||||
setState(() {
|
||||
_selectedVersion = selection.item1;
|
||||
_selectedOption = selection.item2;
|
||||
});
|
||||
builder: (context) => const OperatingSystemSelection(),
|
||||
),
|
||||
);
|
||||
|
||||
if (selection != null) {
|
||||
setState(() {
|
||||
_selectedOperatingSystem = selection;
|
||||
if (selection.versions.length == 1 &&
|
||||
selection.versions.first.options.length == 1) {
|
||||
_selectedVersion = selection.versions.first;
|
||||
_selectedOption = selection.versions.first.options.first;
|
||||
} else {
|
||||
_selectedVersion = null;
|
||||
_selectedOption = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
: null,
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
HomePageButton(
|
||||
label: context.t('Download'),
|
||||
text: context.t('Download'),
|
||||
onPressed: (_selectedVersion == null)
|
||||
? null
|
||||
: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => Downloader(
|
||||
operatingSystem: _selectedOperatingSystem!,
|
||||
version: _selectedVersion!,
|
||||
option: _selectedOption,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
const Expanded(
|
||||
child: ProgressArrow(),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
PlatformText(context.t("Version")),
|
||||
const SizedBox(height: 10),
|
||||
PlatformFilledButton(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.numbers_rounded),
|
||||
const SizedBox(width: 8),
|
||||
PlatformText(_versionButtonLabel),
|
||||
],
|
||||
),
|
||||
isSecondary: _selectedOperatingSystem == null,
|
||||
onPressed: (_selectedOperatingSystem != null)
|
||||
? () {
|
||||
Navigator.of(context)
|
||||
.push<Tuple2<Version, Option?>>(MaterialPageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (context) => VersionSelection(
|
||||
operatingSystem: _selectedOperatingSystem!),
|
||||
))
|
||||
.then((selection) {
|
||||
if (selection != null) {
|
||||
setState(() {
|
||||
_selectedVersion = selection.item1;
|
||||
_selectedOption = selection.item2;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
const Expanded(
|
||||
child: ProgressArrow(),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
PlatformText(context.t('Download')),
|
||||
const SizedBox(height: 10),
|
||||
PlatformFilledButton(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.file_download_outlined),
|
||||
const SizedBox(width: 8),
|
||||
PlatformText(context.t('Download')),
|
||||
],
|
||||
),
|
||||
isSecondary: _selectedVersion == null,
|
||||
onPressed: (_selectedVersion == null)
|
||||
? null
|
||||
: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => Downloader(
|
||||
operatingSystem: _selectedOperatingSystem!,
|
||||
version: _selectedVersion!,
|
||||
option: _selectedOption,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
@ -119,7 +251,7 @@ class _HomePageButtonGroupState extends State<HomePageButtonGroup> {
|
|||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 32),
|
||||
child: Text(context.t('Downloading...'),
|
||||
child: PlatformText(context.t('Downloading...'),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText1
|
||||
|
|
@ -128,7 +260,7 @@ class _HomePageButtonGroupState extends State<HomePageButtonGroup> {
|
|||
const CircularProgressIndicator(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 32),
|
||||
child: Text(
|
||||
child: PlatformText(
|
||||
'Target : ${Directory.current.absolute.path}',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
|
|
@ -167,13 +299,13 @@ class _HomePageButtonGroupState extends State<HomePageButtonGroup> {
|
|||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 32),
|
||||
child: Text(context.t('Done !'),
|
||||
child: PlatformText(context.t('Done !'),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText1
|
||||
?.copyWith(color: Colors.white)),
|
||||
),
|
||||
Text(
|
||||
PlatformText(
|
||||
context.t('Now run {0} to start the VM',
|
||||
args: ["quickemu --vm $operatingSystem-$version"]),
|
||||
style: Theme.of(context)
|
||||
|
|
@ -186,7 +318,7 @@ class _HomePageButtonGroupState extends State<HomePageButtonGroup> {
|
|||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
child: PlatformText(
|
||||
'Dismiss',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:gettext_i18n/gettext_i18n.dart';
|
||||
import 'package:platform_ui/platform_ui.dart';
|
||||
|
||||
import '../../pages/downloader_page.dart';
|
||||
import '../../pages/manager.dart';
|
||||
import '../home_page/home_page_button.dart';
|
||||
|
||||
class MainMenu extends StatelessWidget {
|
||||
const MainMenu({Key? key}) : super(key: key);
|
||||
|
|
@ -11,44 +11,40 @@ class MainMenu extends StatelessWidget {
|
|||
@override
|
||||
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,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
HomePageButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
PageRouteBuilder(
|
||||
fullscreenDialog: true,
|
||||
pageBuilder: (context, animation1, animation2) =>
|
||||
const Manager(),
|
||||
transitionDuration: Duration.zero,
|
||||
),
|
||||
);
|
||||
},
|
||||
text: context.t('Manage existing machines'),
|
||||
),
|
||||
HomePageButton(
|
||||
onPressed: () {
|
||||
//Navigator.of(context).push(MaterialPageRoute(builder: (context) => const DownloaderPage()));
|
||||
Navigator.of(context).push(
|
||||
PageRouteBuilder(
|
||||
fullscreenDialog: true,
|
||||
pageBuilder: (context, animation1, animation2) =>
|
||||
const DownloaderPage(),
|
||||
transitionDuration: Duration.zero,
|
||||
),
|
||||
);
|
||||
},
|
||||
text: context.t('Create new machines'),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
PlatformFilledButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
PageRouteBuilder(
|
||||
fullscreenDialog: true,
|
||||
pageBuilder: (context, animation1, animation2) =>
|
||||
const Manager(),
|
||||
transitionDuration: Duration.zero,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: PlatformText(context.t('Manage existing machines')),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
PlatformFilledButton(
|
||||
onPressed: () {
|
||||
//Navigator.of(context).push(MaterialPageRoute(builder: (context) => const DownloaderPage()));
|
||||
Navigator.of(context).push(
|
||||
PageRouteBuilder(
|
||||
fullscreenDialog: true,
|
||||
pageBuilder: (context, animation1, animation2) =>
|
||||
const DownloaderPage(),
|
||||
transitionDuration: Duration.zero,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: PlatformText(context.t('Create new machines')),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,102 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:gettext_i18n/gettext_i18n.dart';
|
||||
import 'package:quickgui/src/supported_locales.dart';
|
||||
|
||||
import '../globals.dart';
|
||||
import '../mixins/app_version.dart';
|
||||
import '../mixins/preferences_mixin.dart';
|
||||
import '../model/app_settings.dart';
|
||||
|
||||
class LeftMenu extends StatefulWidget {
|
||||
const LeftMenu({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<LeftMenu> createState() => _LeftMenuState();
|
||||
}
|
||||
|
||||
class _LeftMenuState extends State<LeftMenu> with PreferencesMixin {
|
||||
late String currentLocale;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
var appSettings = context.read<AppSettings>();
|
||||
currentLocale = appSettings.activeLocale;
|
||||
if (!supportedLocales.contains(currentLocale)) {
|
||||
currentLocale = currentLocale.split("_")[0];
|
||||
if (!supportedLocales.contains(currentLocale)) {
|
||||
currentLocale = "en";
|
||||
}
|
||||
}
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var _version = AppVersion.packageInfo!.version;
|
||||
return Consumer<AppSettings>(
|
||||
builder: (context, appSettings, _) {
|
||||
return Drawer(
|
||||
child: ListView(
|
||||
children: [
|
||||
ListTile(
|
||||
title: Text("quickgui $_version",
|
||||
style: Theme.of(context).textTheme.headline6),
|
||||
),
|
||||
const Divider(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(context.t('Use dark mode')),
|
||||
Expanded(
|
||||
child: Container(),
|
||||
),
|
||||
Switch(
|
||||
value: Theme.of(context).brightness == Brightness.dark,
|
||||
onChanged: (value) {
|
||||
appSettings.useDarkMode = value;
|
||||
savePreference(prefThemeMode, value);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(context.t('Language')),
|
||||
Expanded(
|
||||
child: Container(),
|
||||
),
|
||||
DropdownButton<String>(
|
||||
value: currentLocale,
|
||||
items: supportedLocales
|
||||
.map(
|
||||
(e) => DropdownMenuItem(child: Text(e), value: e))
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
currentLocale = value!;
|
||||
appSettings.activeLocale = currentLocale;
|
||||
savePreference(prefCurrentLocale, currentLocale);
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:gettext_i18n/gettext_i18n.dart';
|
||||
import 'package:platform_ui/platform_ui.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:quickgui/src/supported_locales.dart';
|
||||
|
||||
import '../globals.dart';
|
||||
import '../mixins/app_version.dart';
|
||||
import '../mixins/preferences_mixin.dart';
|
||||
import '../model/app_settings.dart';
|
||||
|
||||
class SettingsPage extends StatefulWidget {
|
||||
const SettingsPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<SettingsPage> createState() => _SettingsPageState();
|
||||
}
|
||||
|
||||
class _SettingsPageState extends State<SettingsPage> with PreferencesMixin {
|
||||
late String currentLocale;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
var appSettings = context.read<AppSettings>();
|
||||
currentLocale = appSettings.activeLocale;
|
||||
if (!supportedLocales.contains(currentLocale)) {
|
||||
currentLocale = currentLocale.split("_")[0];
|
||||
if (!supportedLocales.contains(currentLocale)) {
|
||||
currentLocale = "en";
|
||||
}
|
||||
}
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var _version = AppVersion.packageInfo!.version;
|
||||
return PlatformScaffold(
|
||||
appBar: PlatformAppBar(
|
||||
title: PlatformText(context.t('Settings')),
|
||||
actions: const [
|
||||
PlatformWindowButtons(),
|
||||
],
|
||||
),
|
||||
body: Consumer<AppSettings>(
|
||||
builder: (context, appSettings, _) {
|
||||
return ListView(
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
Center(
|
||||
child: PlatformText.subheading(
|
||||
"Quickgui $_version",
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Card(
|
||||
color: PlatformTheme.of(context).secondaryBackgroundColor,
|
||||
elevation: platform == TargetPlatform.macOS ? 0 : null,
|
||||
child: PlatformListTile(
|
||||
title: PlatformText(context.t('Use dark mode')),
|
||||
trailing: PlatformDropDownMenu<ThemeMode>(
|
||||
value: appSettings.themeMode,
|
||||
items: [
|
||||
PlatformDropDownMenuItem(
|
||||
child: PlatformText(context.t('System')),
|
||||
value: ThemeMode.system,
|
||||
),
|
||||
PlatformDropDownMenuItem(
|
||||
child: PlatformText(context.t('Light')),
|
||||
value: ThemeMode.light,
|
||||
),
|
||||
PlatformDropDownMenuItem(
|
||||
child: PlatformText(context.t('Dark')),
|
||||
value: ThemeMode.dark,
|
||||
),
|
||||
],
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
appSettings.themeMode = value!;
|
||||
savePreference(
|
||||
prefThemeMode,
|
||||
value.name,
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Card(
|
||||
color: PlatformTheme.of(context).secondaryBackgroundColor,
|
||||
elevation: platform == TargetPlatform.macOS ? 0 : null,
|
||||
child: PlatformListTile(
|
||||
title: PlatformText(context.t('Language')),
|
||||
trailing: PlatformDropDownMenu<String>(
|
||||
value: currentLocale,
|
||||
items: supportedLocales.map(
|
||||
(e) {
|
||||
return PlatformDropDownMenuItem(
|
||||
child: PlatformText(e),
|
||||
value: e,
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
currentLocale = value!;
|
||||
appSettings.activeLocale = currentLocale;
|
||||
savePreference(prefCurrentLocale, currentLocale);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -27,9 +27,14 @@ dependencies:
|
|||
flutter_localizations:
|
||||
sdk: flutter
|
||||
desktop_notifications: ^0.6.1
|
||||
flutter_svg: ^0.23.0
|
||||
flutter_svg: ^1.1.6
|
||||
gettext_i18n: ^1.0.2
|
||||
url_launcher: ^6.1.6
|
||||
platform_ui: ^0.1.0
|
||||
fluent_ui: ^4.1.2
|
||||
macos_ui: ^1.7.5
|
||||
libadwaita: ^1.2.5
|
||||
adwaita: ^0.5.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
|
|||
Loading…
Reference in New Issue