diff --git a/lib/main.dart b/lib/main.dart index 73d9857..738372b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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'; diff --git a/lib/src/app.dart b/lib/src/app.dart index d1043ac..d68cd30 100644 --- a/lib/src/app.dart +++ b/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 with PreferencesMixin { @override Widget build(BuildContext context) { + platform = TargetPlatform.macOS; return FutureBuilder( future: SharedPreferences.getInstance(), builder: (context, snapshot) { @@ -33,47 +36,59 @@ class _AppState extends State 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( - 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 with PreferencesMixin { } }, ); - /* - return FutureBuilder( - future: getPreference(prefThemeMode), - builder: (context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - if (snapshot.data != null) { - context.read().useDarkModeSilently = snapshot.data!; - } - return Consumer( - 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(), - ); - } - }, - ); - */ } } diff --git a/lib/src/model/app_settings.dart b/lib/src/model/app_settings.dart index 0c35e35..cae4a3e 100644 --- a/lib/src/model/app_settings.dart +++ b/lib/src/model/app_settings.dart @@ -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(); } } diff --git a/lib/src/pages/deget_not_found_page.dart b/lib/src/pages/deget_not_found_page.dart index 896fe3d..d9e92f4 100644 --- a/lib/src/pages/deget_not_found_page.dart +++ b/lib/src/pages/deget_not_found_page.dart @@ -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, diff --git a/lib/src/pages/downloader.dart b/lib/src/pages/downloader.dart index c76e21e..d1f944b 100644 --- a/lib/src/pages/downloader.dart +++ b/lib/src/pages/downloader.dart @@ -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 { @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 { : '') ]), ), + actions: const [ + PlatformWindowButtons(), + ], automaticallyImplyLeading: false, ), - body: Column( - children: [ - Expanded( - child: StreamBuilder( - stream: _progressStream, - builder: (context, AsyncSnapshot 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 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), + ], + ), ), ); } diff --git a/lib/src/pages/downloader_page.dart b/lib/src/pages/downloader_page.dart index 16bcb52..62ecf98 100644 --- a/lib/src/pages/downloader_page.dart +++ b/lib/src/pages/downloader_page.dart @@ -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 [ diff --git a/lib/src/pages/main_page.dart b/lib/src/pages/main_page.dart index ca0c469..fdb1004 100644 --- a/lib/src/pages/main_page.dart +++ b/lib/src/pages/main_page.dart @@ -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 { @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(), diff --git a/lib/src/pages/manager.dart b/lib/src/pages/manager.dart index 21dee40..7fde368 100644 --- a/lib/src/pages/manager.dart +++ b/lib/src/pages/manager.dart @@ -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 with PreferencesMixin { Widget _buildVmList() { List _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 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 with PreferencesMixin { } } return [ - ListTile( - title: Text(currentVm), + PlatformListTile( + title: PlatformText(currentVm), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ - 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 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 with PreferencesMixin { onPressed: !active ? null : () { - showDialog( - context: context, - builder: (BuildContext context) => AlertDialog( - title: Text(context.t('Stop The Virtual Machine?')), - content: Text(context.t( + showPlatformAlertDialog( + 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: [ - 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 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( - 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( + 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 with PreferencesMixin { ], )), if (connectInfo.isNotEmpty) - ListTile( - title: Text(connectInfo, style: const TextStyle(fontSize: 12)), - trailing: Row(mainAxisSize: MainAxisSize.min, children: [ - IconButton( + PlatformListTile( + title: + PlatformText(connectInfo, style: const TextStyle(fontSize: 12)), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + PlatformIconButton( icon: Icon( Icons.monitor, color: _spicy ? buttonColor : null, @@ -387,7 +443,7 @@ class _ManagerState extends State 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 with PreferencesMixin { : () { TextEditingController _usernameController = TextEditingController(); - showDialog( - context: context, + showPlatformAlertDialog( + 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: [ - 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 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(), ); diff --git a/lib/src/pages/operating_system_selection.dart b/lib/src/pages/operating_system_selection.dart index 033933d..c824690 100644 --- a/lib/src/pages/operating_system_selection.dart +++ b/lib/src/pages/operating_system_selection.dart @@ -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 { 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: [ - 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); }, diff --git a/lib/src/pages/option_selection.dart b/lib/src/pages/option_selection.dart index 67128d2..a172879 100644 --- a/lib/src/pages/option_selection.dart +++ b/lib/src/pages/option_selection.dart @@ -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 { 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 { 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); }, diff --git a/lib/src/pages/version_selection.dart b/lib/src/pages/version_selection.dart index b6b1818..92215f2 100644 --- a/lib/src/pages/version_selection.dart +++ b/lib/src/pages/version_selection.dart @@ -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 { 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: [ - 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 { 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) diff --git a/lib/src/widgets/downloader/cancel_dismiss_button.dart b/lib/src/widgets/downloader/cancel_dismiss_button.dart index ac5eea9..7077ff5 100644 --- a/lib/src/widgets/downloader/cancel_dismiss_button.dart +++ b/lib/src/widgets/downloader/cancel_dismiss_button.dart @@ -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')), ) ], ), diff --git a/lib/src/widgets/downloader/download_label.dart b/lib/src/widgets/downloader/download_label.dart index 1cacca1..6c82450 100644 --- a/lib/src/widgets/downloader/download_label.dart +++ b/lib/src/widgets/downloader/download_label.dart @@ -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')), ); } } diff --git a/lib/src/widgets/home_page/downloader_menu.dart b/lib/src/widgets/home_page/downloader_menu.dart index bd59cb4..612decc 100644 --- a/lib/src/widgets/home_page/downloader_menu.dart +++ b/lib/src/widgets/home_page/downloader_menu.dart @@ -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 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(), + ) + ], + ), + ), + ], + ), + ], ), ); } diff --git a/lib/src/widgets/home_page/home_page_button.dart b/lib/src/widgets/home_page/home_page_button.dart index 84a0fde..267a78c 100644 --- a/lib/src/widgets/home_page/home_page_button.dart +++ b/lib/src/widgets/home_page/home_page_button.dart @@ -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), ), - ], - ), + ), + ], ), ); } diff --git a/lib/src/widgets/home_page/home_page_button_group.dart b/lib/src/widgets/home_page/home_page_button_group.dart index 9f4d8fb..ea4391f 100644 --- a/lib/src/widgets/home_page/home_page_button_group.dart +++ b/lib/src/widgets/home_page/home_page_button_group.dart @@ -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 { } return Row( children: [ - HomePageButton( - label: context.t("Operating system"), - text: _selectedOperatingSystem?.name ?? context.t('Select...'), - onPressed: () { - Navigator.of(context) - .push(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( + 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>(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>(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 { 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 { 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 { 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 { onPressed: () { Navigator.of(context).pop(); }, - child: Text( + child: PlatformText( 'Dismiss', style: Theme.of(context) .textTheme diff --git a/lib/src/widgets/home_page/main_menu.dart b/lib/src/widgets/home_page/main_menu.dart index 9784421..648c8d3 100644 --- a/lib/src/widgets/home_page/main_menu.dart +++ b/lib/src/widgets/home_page/main_menu.dart @@ -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')), + ), + ], ), ), ); diff --git a/lib/src/widgets/left_menu.dart b/lib/src/widgets/left_menu.dart deleted file mode 100644 index f048efd..0000000 --- a/lib/src/widgets/left_menu.dart +++ /dev/null @@ -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 createState() => _LeftMenuState(); -} - -class _LeftMenuState extends State with PreferencesMixin { - late String currentLocale; - - @override - void initState() { - super.initState(); - } - - @override - void didChangeDependencies() { - var appSettings = context.read(); - 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( - 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( - value: currentLocale, - items: supportedLocales - .map( - (e) => DropdownMenuItem(child: Text(e), value: e)) - .toList(), - onChanged: (value) { - setState(() { - currentLocale = value!; - appSettings.activeLocale = currentLocale; - savePreference(prefCurrentLocale, currentLocale); - }); - }, - ), - ], - ), - ), - ], - ), - ); - }, - ); - } -} diff --git a/lib/src/widgets/settings_page.dart b/lib/src/widgets/settings_page.dart new file mode 100644 index 0000000..a8f3d91 --- /dev/null +++ b/lib/src/widgets/settings_page.dart @@ -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 createState() => _SettingsPageState(); +} + +class _SettingsPageState extends State with PreferencesMixin { + late String currentLocale; + + @override + void initState() { + super.initState(); + } + + @override + void didChangeDependencies() { + var appSettings = context.read(); + 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( + 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( + 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( + 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); + }); + }, + ), + ), + ), + ], + ); + }, + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index d491cff..73b72db 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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: