feat: replace all material widgets with platform specific widgets

This commit is contained in:
Kingkor Roy Tirtho 2022-12-23 00:33:24 +06:00
parent e9cde261b9
commit 3244f42d18
20 changed files with 810 additions and 617 deletions

View File

@ -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';

View File

@ -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(),
);
}
},
);
*/
}
}

View File

@ -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();
}
}

View File

@ -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,

View File

@ -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),
],
),
),
);
}

View File

@ -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 [

View File

@ -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(),

View File

@ -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(),
);

View File

@ -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);
},

View File

@ -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);
},

View File

@ -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)

View File

@ -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')),
)
],
),

View File

@ -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')),
);
}
}

View File

@ -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(),
)
],
),
),
],
),
],
),
);
}

View File

@ -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),
),
],
),
),
],
),
);
}

View File

@ -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

View File

@ -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')),
),
],
),
),
);

View File

@ -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);
});
},
),
],
),
),
],
),
);
},
);
}
}

View File

@ -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);
});
},
),
),
),
],
);
},
),
);
}
}

View File

@ -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: