LCOV - code coverage report
Current view: top level - lib/views - globalsettingsappearanceview.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 284 0.0 %
Date: 2026-07-15 19:28:30 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:io';
       2             : 
       3             : import 'package:flutter/material.dart';
       4             : import 'package:provider/provider.dart';
       5             : import 'package:cwtch/l10n/app_localizations.dart';
       6             : import 'package:path/path.dart' as path;
       7             : 
       8             : import '../config.dart';
       9             : import '../controllers/filesharing.dart';
      10             : import '../cwtch_icons_icons.dart';
      11             : import '../main.dart';
      12             : import '../models/appstate.dart';
      13             : import '../settings.dart';
      14             : import '../themes/cwtch.dart';
      15             : import '../themes/opaque.dart';
      16             : import '../themes/yamltheme.dart';
      17             : import 'globalsettingsview.dart';
      18             : 
      19             : class GlobalSettingsAppearanceView extends StatefulWidget {
      20           0 :   @override
      21           0 :   _GlobalSettingsAppearanceViewState createState() => _GlobalSettingsAppearanceViewState();
      22             : }
      23             : 
      24             : class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceView> {
      25             :   ScrollController settingsListScrollController = ScrollController();
      26             : 
      27           0 :   Widget build(BuildContext context) {
      28           0 :     return Consumer<Settings>(
      29           0 :       builder: (ccontext, settings, child) {
      30           0 :         return LayoutBuilder(
      31           0 :           builder: (BuildContext context, BoxConstraints viewportConstraints) {
      32           0 :             return Scrollbar(
      33           0 :               key: Key("AppearanceSettingsView"),
      34             :               trackVisibility: true,
      35           0 :               controller: settingsListScrollController,
      36           0 :               child: SingleChildScrollView(
      37             :                 clipBehavior: Clip.antiAlias,
      38           0 :                 controller: settingsListScrollController,
      39           0 :                 child: ConstrainedBox(
      40           0 :                   constraints: BoxConstraints(minHeight: viewportConstraints.maxHeight, maxWidth: viewportConstraints.maxWidth),
      41           0 :                   child: Material(
      42           0 :                     color: settings.theme.backgroundPaneColor,
      43           0 :                     child: Column(
      44           0 :                       children: [
      45           0 :                         ListTile(
      46           0 :                           title: Text(AppLocalizations.of(context)!.settingLanguage),
      47           0 :                           leading: Icon(CwtchIcons.change_language, color: settings.current().mainTextColor),
      48           0 :                           trailing: Container(
      49           0 :                             width: MediaQuery.of(context).size.width / 4,
      50           0 :                             child: DropdownButton(
      51           0 :                               key: Key("languagelist"),
      52             :                               isExpanded: true,
      53           0 :                               value: Provider.of<Settings>(context).locale.toString(),
      54           0 :                               onChanged: (String? newValue) {
      55           0 :                                 setState(() {
      56           0 :                                   EnvironmentConfig.debugLog("setting language: $newValue");
      57           0 :                                   settings.switchLocaleByCode(newValue!);
      58           0 :                                   saveSettings(context);
      59             :                                 });
      60             :                               },
      61           0 :                               items: AppLocalizations.supportedLocales.map<DropdownMenuItem<String>>((Locale value) {
      62           0 :                                 return DropdownMenuItem<String>(
      63           0 :                                   value: value.toString(),
      64           0 :                                   child: Text(
      65           0 :                                     key: Key("dropdownLanguage" + value.languageCode),
      66           0 :                                     getLanguageFull(context, value.languageCode, value.countryCode),
      67           0 :                                     style: settings.scaleFonts(defaultDropDownMenuItemTextStyle),
      68             :                                     overflow: TextOverflow.ellipsis,
      69             :                                   ),
      70             :                                 );
      71           0 :                               }).toList(),
      72             :                             ),
      73             :                           ),
      74             :                         ),
      75           0 :                         SwitchListTile(
      76           0 :                           title: Text(AppLocalizations.of(context)!.settingTheme),
      77           0 :                           value: settings.current().mode == mode_light,
      78           0 :                           onChanged: (bool value) {
      79             :                             if (value) {
      80           0 :                               settings.setTheme(settings.themeId ?? "cwtch", mode_light);
      81             :                             } else {
      82           0 :                               settings.setTheme(settings.themeId ?? "cwtch", mode_dark);
      83             :                             }
      84             : 
      85             :                             // Save Settings...
      86           0 :                             saveSettings(context);
      87             :                           },
      88           0 :                           activeTrackColor: settings.theme.defaultButtonColor,
      89           0 :                           inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
      90           0 :                           secondary: Icon(CwtchIcons.change_theme, color: settings.current().mainTextColor),
      91             :                         ),
      92           0 :                         ListTile(
      93           0 :                           title: Text(AppLocalizations.of(context)!.themeColorLabel),
      94           0 :                           trailing: Container(
      95           0 :                             width: MediaQuery.of(context).size.width / 4,
      96           0 :                             child: DropdownButton<String>(
      97           0 :                               key: Key("DropdownTheme"),
      98             :                               isExpanded: true,
      99           0 :                               value: Provider.of<Settings>(context).themeId,
     100           0 :                               onChanged: (String? newValue) {
     101           0 :                                 setState(() {
     102           0 :                                   settings.setTheme(newValue!, settings.theme.mode);
     103           0 :                                   saveSettings(context);
     104             :                                 });
     105             :                               },
     106           0 :                               items: settings.themeloader.themes.keys.map<DropdownMenuItem<String>>((String themeId) {
     107           0 :                                 return DropdownMenuItem<String>(
     108             :                                   value: themeId,
     109           0 :                                   child: Text(getThemeName(context, settings, themeId), style: settings.scaleFonts(defaultDropDownMenuItemTextStyle)), //"ddi_$themeId", key: Key("ddi_$themeId")),
     110             :                                 );
     111           0 :                               }).toList(),
     112             :                             ),
     113             :                           ),
     114           0 :                           leading: Icon(Icons.palette, color: settings.current().mainTextColor),
     115             :                         ),
     116           0 :                         Visibility(
     117             :                           // TODO: Android support needs gomobile support for reading / writing themes, and ideally importing from a .zip or .tar.gz
     118           0 :                           visible: !Platform.isAndroid,
     119           0 :                           child: ListTile(
     120           0 :                             leading: Icon(Icons.palette, color: Provider.of<Settings>(context).theme.messageFromMeTextColor),
     121           0 :                             title: Text(AppLocalizations.of(context)!.settingsImportThemeTitle),
     122           0 :                             subtitle: Text(AppLocalizations.of(context)!.settingsImportThemeDescription),
     123             :                             //AppLocalizations.of(
     124             :                             //context)!
     125             :                             //.fileSharingSettingsDownloadFolderDescription,
     126           0 :                             trailing: Container(
     127           0 :                               width: MediaQuery.of(context).size.width / 4,
     128           0 :                               child: OutlinedButton.icon(
     129           0 :                                 label: Text(AppLocalizations.of(context)!.settingsImportThemeButton),
     130           0 :                                 onPressed: Provider.of<AppState>(context).disableFilePicker
     131             :                                     ? null
     132           0 :                                     : () async {
     133           0 :                                         if (Platform.isAndroid) {
     134             :                                           return;
     135             :                                         }
     136           0 :                                         var selectedDirectory = await showSelectDirectoryPicker(context);
     137             :                                         if (selectedDirectory != null) {
     138           0 :                                           selectedDirectory += path.separator;
     139           0 :                                           final customThemeDir = path.join(await Provider.of<FlwtchState>(context, listen: false).cwtch.getCwtchDir(), custom_themes_subdir);
     140           0 :                                           importThemeCheck(context, settings, customThemeDir, selectedDirectory);
     141             :                                         } else {
     142             :                                           // User canceled the picker
     143             :                                         }
     144             :                                       },
     145             :                                 //onChanged: widget.onSave,
     146           0 :                                 icon: Icon(Icons.folder),
     147             :                                 //tooltip: widget.tooltip,
     148             :                               ),
     149             :                             ),
     150             :                           ),
     151             :                         ),
     152           0 :                         SwitchListTile(
     153           0 :                           title: Text(AppLocalizations.of(context)!.settingsThemeImages),
     154           0 :                           subtitle: Text(AppLocalizations.of(context)!.settingsThemeImagesDescription),
     155           0 :                           value: settings.themeImages,
     156           0 :                           onChanged: (bool value) {
     157           0 :                             settings.themeImages = value; // Save Settings...
     158           0 :                             saveSettings(context);
     159             :                           },
     160           0 :                           activeTrackColor: settings.theme.defaultButtonColor,
     161           0 :                           inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
     162           0 :                           secondary: Icon(Icons.image, color: settings.current().mainTextColor),
     163             :                         ),
     164           0 :                         ListTile(
     165           0 :                           title: Text(AppLocalizations.of(context)!.settingUIColumnPortrait),
     166           0 :                           leading: Icon(Icons.table_chart, color: settings.current().mainTextColor),
     167           0 :                           trailing: Container(
     168           0 :                             width: MediaQuery.of(context).size.width / 4,
     169           0 :                             child: DropdownButton(
     170             :                               isExpanded: true,
     171           0 :                               value: settings.uiColumnModePortrait.toString(),
     172           0 :                               onChanged: (String? newValue) {
     173           0 :                                 settings.uiColumnModePortrait = Settings.uiColumnModeFromString(newValue!);
     174           0 :                                 saveSettings(context);
     175             :                               },
     176           0 :                               items: Settings.uiColumnModeOptions(false).map<DropdownMenuItem<String>>((DualpaneMode value) {
     177           0 :                                 return DropdownMenuItem<String>(
     178           0 :                                   value: value.toString(),
     179           0 :                                   child: Text(Settings.uiColumnModeToString(value, context), style: settings.scaleFonts(defaultDropDownMenuItemTextStyle)),
     180             :                                 );
     181           0 :                               }).toList(),
     182             :                             ),
     183             :                           ),
     184             :                         ),
     185           0 :                         ListTile(
     186           0 :                           title: Text(AppLocalizations.of(context)!.settingUIColumnLandscape, textWidthBasis: TextWidthBasis.longestLine, softWrap: true),
     187           0 :                           leading: Icon(Icons.stay_primary_landscape, color: settings.current().mainTextColor),
     188           0 :                           trailing: Container(
     189           0 :                             width: MediaQuery.of(context).size.width / 4,
     190           0 :                             child: Container(
     191           0 :                               width: MediaQuery.of(context).size.width / 4,
     192           0 :                               child: DropdownButton(
     193             :                                 isExpanded: true,
     194           0 :                                 value: settings.uiColumnModeLandscape.toString(),
     195           0 :                                 onChanged: (String? newValue) {
     196           0 :                                   settings.uiColumnModeLandscape = Settings.uiColumnModeFromString(newValue!);
     197           0 :                                   saveSettings(context);
     198             :                                 },
     199           0 :                                 items: Settings.uiColumnModeOptions(true).map<DropdownMenuItem<String>>((DualpaneMode value) {
     200           0 :                                   return DropdownMenuItem<String>(
     201           0 :                                     value: value.toString(),
     202           0 :                                     child: Text(Settings.uiColumnModeToString(value, context), overflow: TextOverflow.ellipsis, style: settings.scaleFonts(defaultDropDownMenuItemTextStyle)),
     203             :                                   );
     204           0 :                                 }).toList(),
     205             :                               ),
     206             :                             ),
     207             :                           ),
     208             :                         ),
     209           0 :                         ListTile(
     210           0 :                           title: Text(AppLocalizations.of(context)!.defaultScalingText),
     211           0 :                           subtitle: Text(AppLocalizations.of(context)!.fontScalingDescription),
     212           0 :                           trailing: Container(
     213           0 :                             width: MediaQuery.of(context).size.width / 4,
     214           0 :                             child: Slider(
     215           0 :                               onChanged: (double value) {
     216           0 :                                 settings.fontScaling = value;
     217             :                                 // Save Settings...
     218           0 :                                 saveSettings(context);
     219           0 :                                 EnvironmentConfig.debugLog("Font Scaling: $value");
     220             :                               },
     221             :                               min: 0.5,
     222             :                               divisions: 12,
     223             :                               max: 2.0,
     224           0 :                               label: '${settings.fontScaling * 100}%',
     225           0 :                               value: settings.fontScaling,
     226             :                             ),
     227             :                           ),
     228           0 :                           leading: Icon(Icons.format_size, color: settings.current().mainTextColor),
     229             :                         ),
     230           0 :                         SwitchListTile(
     231           0 :                           title: Text(AppLocalizations.of(context)!.streamerModeLabel),
     232           0 :                           subtitle: Text(AppLocalizations.of(context)!.descriptionStreamerMode),
     233           0 :                           value: settings.streamerMode,
     234           0 :                           onChanged: (bool value) {
     235           0 :                             settings.setStreamerMode(value);
     236             :                             // Save Settings...
     237           0 :                             saveSettings(context);
     238             :                           },
     239           0 :                           activeTrackColor: settings.theme.defaultButtonColor,
     240           0 :                           inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
     241           0 :                           secondary: Icon(CwtchIcons.streamer_bunnymask, color: settings.current().mainTextColor),
     242             :                         ),
     243           0 :                         SwitchListTile(
     244           0 :                           title: Text(AppLocalizations.of(context)!.formattingExperiment),
     245           0 :                           subtitle: Text(AppLocalizations.of(context)!.messageFormattingDescription),
     246           0 :                           value: settings.isExperimentEnabled(FormattingExperiment),
     247           0 :                           onChanged: (bool value) {
     248             :                             if (value) {
     249           0 :                               settings.enableExperiment(FormattingExperiment);
     250             :                             } else {
     251           0 :                               settings.disableExperiment(FormattingExperiment);
     252             :                             }
     253           0 :                             saveSettings(context);
     254             :                           },
     255           0 :                           activeTrackColor: settings.theme.defaultButtonColor,
     256           0 :                           inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
     257           0 :                           secondary: Icon(Icons.text_fields, color: settings.current().mainTextColor),
     258             :                         ),
     259             :                       ],
     260             :                     ),
     261             :                   ),
     262             :                 ),
     263             :               ),
     264             :             );
     265             :           },
     266             :         );
     267             :       },
     268             :     );
     269             :   }
     270             : 
     271             :   /// A slightly verbose way to extract the full language name from
     272             :   /// an individual language code. There might be a more efficient way of doing this.
     273           0 :   String getLanguageFull(context, String languageCode, String? countryCode) {
     274           0 :     if (languageCode == "en") {
     275           0 :       return AppLocalizations.of(context)!.localeEn;
     276             :     }
     277           0 :     if (languageCode == "es") {
     278           0 :       return AppLocalizations.of(context)!.localeEs;
     279             :     }
     280           0 :     if (languageCode == "fr") {
     281           0 :       return AppLocalizations.of(context)!.localeFr;
     282             :     }
     283           0 :     if (languageCode == "pt" && countryCode == "BR") {
     284           0 :       return AppLocalizations.of(context)!.localePtBr;
     285             :     }
     286           0 :     if (languageCode == "pt") {
     287           0 :       return AppLocalizations.of(context)!.localePt;
     288             :     }
     289           0 :     if (languageCode == "de") {
     290           0 :       return AppLocalizations.of(context)!.localeDe;
     291             :     }
     292           0 :     if (languageCode == "el") {
     293           0 :       return AppLocalizations.of(context)!.localeEl;
     294             :     }
     295           0 :     if (languageCode == "it") {
     296           0 :       return AppLocalizations.of(context)!.localeIt;
     297             :     }
     298           0 :     if (languageCode == "no") {
     299           0 :       return AppLocalizations.of(context)!.localeNo;
     300             :     }
     301           0 :     if (languageCode == "pl") {
     302           0 :       return AppLocalizations.of(context)!.localePl;
     303             :     }
     304           0 :     if (languageCode == "lb") {
     305           0 :       return AppLocalizations.of(context)!.localeLb;
     306             :     }
     307           0 :     if (languageCode == "ru") {
     308           0 :       return AppLocalizations.of(context)!.localeRU;
     309             :     }
     310           0 :     if (languageCode == "ro") {
     311           0 :       return AppLocalizations.of(context)!.localeRo;
     312             :     }
     313           0 :     if (languageCode == "cy") {
     314           0 :       return AppLocalizations.of(context)!.localeCy;
     315             :     }
     316           0 :     if (languageCode == "da") {
     317           0 :       return AppLocalizations.of(context)!.localeDa;
     318             :     }
     319           0 :     if (languageCode == "tr") {
     320           0 :       return AppLocalizations.of(context)!.localeTr;
     321             :     }
     322           0 :     if (languageCode == "nl") {
     323           0 :       return AppLocalizations.of(context)!.localeNl;
     324             :     }
     325           0 :     if (languageCode == "sk") {
     326           0 :       return AppLocalizations.of(context)!.localeSk;
     327             :     }
     328           0 :     if (languageCode == "ko") {
     329           0 :       return AppLocalizations.of(context)!.localeKo;
     330             :     }
     331           0 :     if (languageCode == "ja") {
     332           0 :       return AppLocalizations.of(context)!.localeJa;
     333             :     }
     334           0 :     if (languageCode == "sv") {
     335           0 :       return AppLocalizations.of(context)!.localeSv;
     336             :     }
     337           0 :     if (languageCode == "sw") {
     338           0 :       return AppLocalizations.of(context)!.localeSw;
     339             :     }
     340           0 :     if (languageCode == "uk") {
     341           0 :       return AppLocalizations.of(context)!.localeUk;
     342             :     }
     343           0 :     if (languageCode == "uz") {
     344           0 :       return AppLocalizations.of(context)!.localeUzbek;
     345             :     }
     346             :     return languageCode;
     347             :   }
     348             : 
     349             :   /// Since we don't seem to able to dynamically pull translations, this function maps themes to their names
     350           0 :   String getThemeName(context, Settings settings, String theme) {
     351             :     switch (theme) {
     352           0 :       case cwtch_theme:
     353           0 :         return AppLocalizations.of(context)!.themeNameCwtch;
     354           0 :       case "ghost":
     355           0 :         return AppLocalizations.of(context)!.themeNameGhost;
     356           0 :       case "mermaid":
     357           0 :         return AppLocalizations.of(context)!.themeNameMermaid;
     358           0 :       case "midnight":
     359           0 :         return AppLocalizations.of(context)!.themeNameMidnight;
     360           0 :       case "neon1":
     361           0 :         return AppLocalizations.of(context)!.themeNameNeon1;
     362           0 :       case "neon2":
     363           0 :         return AppLocalizations.of(context)!.themeNameNeon2;
     364           0 :       case "pumpkin":
     365           0 :         return AppLocalizations.of(context)!.themeNamePumpkin;
     366           0 :       case "vampire":
     367           0 :         return AppLocalizations.of(context)!.themeNameVampire;
     368           0 :       case "witch":
     369           0 :         return AppLocalizations.of(context)!.themeNameWitch;
     370           0 :       case "juniper":
     371             :         return "Juniper"; // Juniper is a noun, and doesn't get subject to translation...
     372             :     }
     373           0 :     return settings.themeloader.themes[theme]?[mode_light]?.theme ?? settings.themeloader.themes[theme]?[mode_dark]?.theme ?? theme;
     374             :   }
     375             : 
     376           0 :   void importThemeCheck(BuildContext context, Settings settings, String themesDir, String newThemeDirectory) async {
     377             :     // check is theme
     378           0 :     final srcDir = Directory(newThemeDirectory);
     379           0 :     String themeName = path.basename(newThemeDirectory);
     380             : 
     381           0 :     File themeFile = File(path.join(newThemeDirectory, "theme.yml"));
     382             : 
     383           0 :     if (!themeFile.existsSync()) {
     384             :       // error, isnt valid theme, no .yml theme file found
     385           0 :       SnackBar err = SnackBar(content: Text(AppLocalizations.of(context)!.settingsThemeErrorInvalid.replaceAll("\$themeName", themeName)));
     386           0 :       ScaffoldMessenger.of(context).showSnackBar(err);
     387             :       return;
     388             :     }
     389             : 
     390           0 :     Directory targetDir = Directory(path.join(themesDir, themeName));
     391             :     // check if exists
     392           0 :     if (settings.themeloader.themes.containsKey(themeName) || targetDir.existsSync()) {
     393           0 :       _modalConfirmOverwriteCustomTheme(srcDir, targetDir, themesDir, themeName, settings);
     394             :     } else {
     395           0 :       importTheme(srcDir, targetDir, themesDir, themeName, settings);
     396             :     }
     397             :   }
     398             : 
     399           0 :   void importTheme(Directory srcDir, Directory targetDir, String themesDir, String themeName, Settings settings) async {
     400           0 :     if (!targetDir.existsSync()) {
     401           0 :       targetDir.createSync();
     402             :     }
     403             : 
     404           0 :     await for (var entity in srcDir.list(recursive: false)) {
     405           0 :       if (entity is File) {
     406           0 :         entity.copySync(path.join(targetDir.path, path.basename(entity.path)));
     407             :       }
     408             :     }
     409             : 
     410           0 :     var data = await loadFileYamlTheme(path.join(targetDir.path, "theme.yml"), targetDir.path);
     411             :     if (data != null) {
     412           0 :       settings.themeloader.themes[themeName] = data;
     413             :     }
     414             : 
     415           0 :     var themeId = data?[mode_light]?.theme ?? data?[mode_dark]?.theme ?? themeName;
     416           0 :     if (settings.current().theme == themeId) {
     417           0 :       PaintingBinding.instance.imageCache.clear();
     418           0 :       settings.setTheme(themeName, settings.current().mode);
     419             :     }
     420             :   }
     421             : 
     422           0 :   void _modalConfirmOverwriteCustomTheme(Directory srcDir, Directory targetDir, String themesDir, String themeName, Settings settings) {
     423           0 :     showModalBottomSheet<void>(
     424           0 :       context: context,
     425             :       isScrollControlled: true,
     426           0 :       builder: (BuildContext context) {
     427           0 :         return Padding(
     428           0 :           padding: MediaQuery.of(context).viewInsets,
     429           0 :           child: RepaintBoundary(
     430           0 :             child: Container(
     431           0 :               height: Platform.isAndroid ? 250 : 200, // bespoke value courtesy of the [TextField] docs
     432           0 :               child: Center(
     433           0 :                 child: Padding(
     434           0 :                   padding: EdgeInsets.all(10.0),
     435           0 :                   child: Column(
     436             :                     mainAxisAlignment: MainAxisAlignment.center,
     437             :                     mainAxisSize: MainAxisSize.min,
     438           0 :                     children: <Widget>[
     439           0 :                       Text(AppLocalizations.of(context)!.settingThemeOverwriteQuestion.replaceAll("\$themeName", themeName)),
     440           0 :                       SizedBox(height: 20),
     441           0 :                       Row(
     442             :                         mainAxisAlignment: MainAxisAlignment.spaceEvenly,
     443           0 :                         children: [
     444           0 :                           Spacer(),
     445           0 :                           Expanded(
     446           0 :                             child: ElevatedButton(
     447           0 :                               child: Text(AppLocalizations.of(context)!.settingThemeOverwriteConfirm, semanticsLabel: AppLocalizations.of(context)!.settingThemeOverwriteConfirm),
     448           0 :                               onPressed: () {
     449           0 :                                 importTheme(srcDir, targetDir, themesDir, themeName, settings);
     450             : 
     451           0 :                                 Navigator.pop(context);
     452             :                               },
     453             :                             ),
     454             :                           ),
     455           0 :                           SizedBox(width: 20),
     456           0 :                           Expanded(
     457           0 :                             child: ElevatedButton(
     458           0 :                               child: Text(AppLocalizations.of(context)!.cancel, semanticsLabel: AppLocalizations.of(context)!.cancel),
     459           0 :                               onPressed: () {
     460           0 :                                 Navigator.pop(context);
     461             :                               },
     462             :                             ),
     463             :                           ),
     464           0 :                           Spacer(),
     465             :                         ],
     466             :                       ),
     467             :                     ],
     468             :                   ),
     469             :                 ),
     470             :               ),
     471             :             ),
     472             :           ),
     473             :         );
     474             :       },
     475             :     );
     476             :   }
     477             : }

Generated by: LCOV version 1.14