diff --git a/lib/main.dart b/lib/main.dart index 5ed0a29..a03ffc7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,12 +2,13 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:provider/provider.dart'; +import 'package:quickgui/src/globals.dart'; import 'package:tuple/tuple.dart'; import 'package:window_size/window_size.dart'; import 'src/app.dart'; import 'src/mixins/app_version.dart'; -import 'src/model/app_theme.dart'; +import 'src/model/app_settings.dart'; import 'src/model/operating_system.dart'; import 'src/model/option.dart'; import 'src/model/version.dart'; @@ -20,7 +21,12 @@ Future> loadOperatingSystems(bool showUbuntus) async { OperatingSystem? currentOperatingSystem; Version? currentVersion; - stdout.split('\n').skip(1).where((element) => element.isNotEmpty).map((e) => e.trim()).forEach((element) { + stdout + .split('\n') + .skip(1) + .where((element) => element.isNotEmpty) + .map((e) => e.trim()) + .forEach((element) { var chunks = element.split(","); Tuple5 supportedVersion; if (chunks.length == 4) // Legacy version of quickget @@ -32,7 +38,8 @@ Future> loadOperatingSystems(bool showUbuntus) async { } if (currentOperatingSystem?.code != supportedVersion.item2) { - currentOperatingSystem = OperatingSystem(supportedVersion.item1, supportedVersion.item2); + currentOperatingSystem = + OperatingSystem(supportedVersion.item1, supportedVersion.item2); output.add(currentOperatingSystem!); currentVersion = null; } @@ -40,7 +47,8 @@ Future> loadOperatingSystems(bool showUbuntus) async { currentVersion = Version(supportedVersion.item3); currentOperatingSystem!.versions.add(currentVersion!); } - currentVersion!.options.add(Option(supportedVersion.item4, supportedVersion.item5)); + currentVersion!.options + .add(Option(supportedVersion.item4, supportedVersion.item5)); }); return output; @@ -48,6 +56,7 @@ Future> loadOperatingSystems(bool showUbuntus) async { void main() async { WidgetsFlutterBinding.ensureInitialized(); + // Don't forget to also change the size in linux/my_application.cc:50 setWindowMinSize(const Size(692, 580)); setWindowMaxSize(const Size(692, 580)); gOperatingSystems = await loadOperatingSystems(false); @@ -55,7 +64,7 @@ void main() async { runApp( MultiProvider( providers: [ - ChangeNotifierProvider(create: (_) => AppTheme()), + ChangeNotifierProvider(create: (_) => AppSettings()), ], builder: (context, _) => const App(), ), diff --git a/lib/src/app.dart b/lib/src/app.dart index d27920b..acfd85b 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -1,12 +1,16 @@ +import 'dart:ffi'; +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:provider/provider.dart'; import 'package:gettext_i18n/gettext_i18n.dart'; -import 'package:window_size/window_size.dart'; +import 'package:quickgui/src/supported_locales.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'globals.dart'; import 'mixins/preferences_mixin.dart'; -import 'model/app_theme.dart'; +import 'model/app_settings.dart'; import 'pages/main_page.dart'; class App extends StatefulWidget { @@ -19,56 +23,33 @@ class App extends StatefulWidget { class _AppState extends State with PreferencesMixin { @override Widget build(BuildContext context) { - 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( + return FutureBuilder( + future: SharedPreferences.getInstance(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.data != null) { + var appSettings = context.read(); + appSettings.setActiveLocaleSilently( + snapshot.data?.getString(prefCurrentLocale) ?? + Platform.localeName); + appSettings.useDarkModeSilently = + snapshot.data!.getBool(prefThemeMode) as bool; + return Consumer( + builder: (context, appSettings, _) => MaterialApp( theme: ThemeData(primarySwatch: Colors.pink), darkTheme: ThemeData.dark(), - themeMode: appTheme.themeMode, + themeMode: appSettings.themeMode, home: const MainPage(), - supportedLocales: const [ - /* - * List of locales (language + country) we have translations for. - * - * If there is a file for the tuple (langue, country) in assets/lib/i18n, then this - * will be used for translation. - * - * If there is not, then we'll look for a file for the language only. - * - * If there is no file for the language code, we'll fallback to the english file. - * - * Example : let's say the locale is fr_CH. We will look for "assets/lib/i18n/fr_CH.po", - * "assets/lib/i18n/fr.po", and "assets/lib/i18n/en.po", stopping at the first file we - * find. - * - * Translation files are not merged, meaning if some translations are missing in fr_CH.po - * but are present in fr.po, the missing translations will not be picked up from fr.po, - * and thus will show up in english. - */ - Locale('cs_CZ'), - Locale('cy'), - Locale('de'), - Locale('en'), - Locale('fr'), - Locale('fr', 'CH'), - Locale('gd'), - Locale('it'), - Locale('nl'), - Locale('no'), - Locale('oc'), - Locale('ru'), - ], + supportedLocales: supportedLocales.map((s) => s.contains("_") + ? Locale(s.split("_")[0], s.split("_")[1]) + : Locale(s)), localizationsDelegates: [ GettextLocalizationsDelegate(), GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ], + locale: + Locale(appSettings.languageCode!, appSettings.countryCode), localeListResolutionCallback: (locales, supportedLocales) { if (locales != null) { for (var locale in locales) { @@ -96,5 +77,56 @@ 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/globals.dart b/lib/src/globals.dart index edb9231..3032be2 100644 --- a/lib/src/globals.dart +++ b/lib/src/globals.dart @@ -3,3 +3,4 @@ import 'dart:io'; var gIsSnap = Platform.environment['SNAP']?.isNotEmpty ?? false; const String prefWorkingDirectory = 'workingDirectory'; const String prefThemeMode = 'themeMode'; +const String prefCurrentLocale = 'currentLocale'; diff --git a/lib/src/model/app_settings.dart b/lib/src/model/app_settings.dart new file mode 100644 index 0000000..0c35e35 --- /dev/null +++ b/lib/src/model/app_settings.dart @@ -0,0 +1,48 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:quickgui/src/supported_locales.dart'; + +class AppSettings extends ChangeNotifier { + ThemeMode? _themeMode; + String? _activeLocale; + + ThemeMode get themeMode => _themeMode ?? ThemeMode.system; + + String get activeLocale => _activeLocale ?? Platform.localeName; + + String? get languageCode => _activeLocale?.split("_")[0]; + String? get countryCode => + ((_activeLocale != null) && (_activeLocale!.contains("_"))) + ? _activeLocale!.split("_")[1] + : null; + + set activeLocale(String locale) { + _activeLocale = locale; + notifyListeners(); + } + + setActiveLocaleSilently(String locale) { + _activeLocale = locale; + if (_activeLocale!.contains(".")) { + _activeLocale = _activeLocale!.split(".")[0]; + } + if (!supportedLocales.contains(_activeLocale)) { + if (activeLocale.contains("_")) { + _activeLocale = _activeLocale!.split("_")[0]; + } + if (!supportedLocales.contains(_activeLocale)) { + _activeLocale = "en"; + } + } + } + + set useDarkModeSilently(bool useDarkMode) { + _themeMode = useDarkMode ? ThemeMode.dark : ThemeMode.light; + } + + set useDarkMode(bool useDarkMode) { + useDarkModeSilently = useDarkMode; + notifyListeners(); + } +} diff --git a/lib/src/model/app_theme.dart b/lib/src/model/app_theme.dart deleted file mode 100644 index 6026060..0000000 --- a/lib/src/model/app_theme.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter/material.dart'; - -class AppTheme extends ChangeNotifier { - ThemeMode? _themeMode; - - ThemeMode get themeMode => _themeMode ?? ThemeMode.system; - - set useDarkModeSilently(bool useDarkMode) { - _themeMode = useDarkMode ? ThemeMode.dark : ThemeMode.light; - } - - set useDarkMode(bool useDarkMode) { - useDarkModeSilently = useDarkMode; - notifyListeners(); - } -} diff --git a/lib/src/supported_locales.dart b/lib/src/supported_locales.dart new file mode 100644 index 0000000..73f3371 --- /dev/null +++ b/lib/src/supported_locales.dart @@ -0,0 +1,31 @@ +/* + * List of locales (language + country) we have translations for. + * + * If there is a file for the tuple (langue, country) in assets/lib/i18n, then this + * will be used for translation. + * + * If there is not, then we'll look for a file for the language only. + * + * If there is no file for the language code, we'll fallback to the english file. + * + * Example : let's say the locale is fr_CH. We will look for "assets/lib/i18n/fr_CH.po", + * "assets/lib/i18n/fr.po", and "assets/lib/i18n/en.po", stopping at the first file we + * find. + * + * Translation files are not merged, meaning if some translations are missing in fr_CH.po + * but are present in fr.po, the missing translations will not be picked up from fr.po, + * and thus will show up in english. + */ +final supportedLocales = [ + 'cs_CZ', + 'cy', + 'de', + 'en', + 'fr', + 'gd', + 'it', + 'nl', + 'no', + 'oc', + 'ru', +]; diff --git a/lib/src/widgets/left_menu.dart b/lib/src/widgets/left_menu.dart index d38cd7b..f048efd 100644 --- a/lib/src/widgets/left_menu.dart +++ b/lib/src/widgets/left_menu.dart @@ -1,45 +1,102 @@ -import 'package:flutter/foundation.dart'; 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_theme.dart'; +import '../model/app_settings.dart'; -class LeftMenu extends StatelessWidget with PreferencesMixin { +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, appTheme, _) => 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')), - Switch( - value: Theme.of(context).brightness == Brightness.dark, - onChanged: (value) { - appTheme.useDarkMode = value; - savePreference(prefThemeMode, value); - }, - ), - ], + 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/linux/my_application.cc b/linux/my_application.cc index 4372fc4..26b703b 100644 --- a/linux/my_application.cc +++ b/linux/my_application.cc @@ -47,7 +47,7 @@ static void my_application_activate(GApplication* application) { gtk_window_set_title(window, "quickgui"); } - gtk_window_set_default_size(window, 640, 480); + gtk_window_set_default_size(window, 692, 580); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new();