cwtch-ui/lib/views/globalsettingsappearancevie...

451 lines
24 KiB
Dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:path/path.dart' as path;
import '../config.dart';
import '../controllers/filesharing.dart';
import '../cwtch_icons_icons.dart';
import '../main.dart';
import '../models/appstate.dart';
import '../settings.dart';
import '../themes/cwtch.dart';
import '../themes/opaque.dart';
import '../themes/yamltheme.dart';
import 'globalsettingsview.dart';
class GlobalSettingsAppearanceView extends StatefulWidget {
@override
_GlobalSettingsAppearanceViewState createState() => _GlobalSettingsAppearanceViewState();
}
class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceView> {
ScrollController settingsListScrollController = ScrollController();
Widget build(BuildContext context) {
return Consumer<Settings>(builder: (ccontext, settings, child) {
return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) {
return Scrollbar(
key: Key("AppearanceSettingsView"),
trackVisibility: true,
controller: settingsListScrollController,
child: SingleChildScrollView(
clipBehavior: Clip.antiAlias,
controller: settingsListScrollController,
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: viewportConstraints.maxHeight, maxWidth: viewportConstraints.maxWidth),
child: Container(
color: settings.theme.backgroundPaneColor,
child: Column(children: [
ListTile(
title: Text(AppLocalizations.of(context)!.settingLanguage),
leading: Icon(CwtchIcons.change_language, color: settings.current().mainTextColor),
trailing: Container(
width: MediaQuery.of(context).size.width / 4,
child: DropdownButton(
key: Key("languagelist"),
isExpanded: true,
value: Provider.of<Settings>(context).locale.toString(),
onChanged: (String? newValue) {
setState(() {
EnvironmentConfig.debugLog("setting language: $newValue");
settings.switchLocaleByCode(newValue!);
saveSettings(context);
});
},
items: AppLocalizations.supportedLocales.map<DropdownMenuItem<String>>((Locale value) {
return DropdownMenuItem<String>(
value: value.toString(),
child: Text(
key: Key("dropdownLanguage" + value.languageCode),
getLanguageFull(context, value.languageCode, value.countryCode),
style: settings.scaleFonts(defaultDropDownMenuItemTextStyle),
overflow: TextOverflow.ellipsis,
),
);
}).toList()))),
SwitchListTile(
title: Text(AppLocalizations.of(context)!.settingTheme),
value: settings.current().mode == mode_light,
onChanged: (bool value) {
if (value) {
settings.setTheme(settings.theme.theme, mode_light);
} else {
settings.setTheme(settings.theme.theme, mode_dark);
}
// Save Settings...
saveSettings(context);
},
activeTrackColor: settings.theme.defaultButtonColor,
inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
secondary: Icon(CwtchIcons.change_theme, color: settings.current().mainTextColor),
),
ListTile(
title: Text(AppLocalizations.of(context)!.themeColorLabel),
trailing: Container(
width: MediaQuery.of(context).size.width / 4,
child: DropdownButton<String>(
key: Key("DropdownTheme"),
isExpanded: true,
value: Provider.of<Settings>(context).themeId,
onChanged: (String? newValue) {
setState(() {
settings.setTheme(newValue!, settings.theme.mode);
saveSettings(context);
});
},
items: settings.themeloader.themes.keys.map<DropdownMenuItem<String>>((String themeId) {
return DropdownMenuItem<String>(
value: themeId,
child:
Text(getThemeName(context, settings, themeId), style: settings.scaleFonts(defaultDropDownMenuItemTextStyle)), //"ddi_$themeId", key: Key("ddi_$themeId")),
);
}).toList())),
leading: Icon(Icons.palette, color: settings.current().mainTextColor),
),
Visibility(
// TODO: Android support needs gomobile support for reading / writing themes, and ideally importing from a .zip or .tar.gz
visible: !Platform.isAndroid,
child: ListTile(
leading: Icon(Icons.palette, color: Provider.of<Settings>(context).theme.messageFromMeTextColor),
title: Text(AppLocalizations.of(context)!.settingsImportThemeTitle),
subtitle: Text(AppLocalizations.of(context)!.settingsImportThemeDescription),
//AppLocalizations.of(
//context)!
//.fileSharingSettingsDownloadFolderDescription,
trailing: Container(
width: MediaQuery.of(context).size.width / 4,
child: ElevatedButton.icon(
label: Text(AppLocalizations.of(context)!.settingsImportThemeButton),
onPressed: Provider.of<AppState>(context).disableFilePicker
? null
: () async {
if (Platform.isAndroid) {
return;
}
var selectedDirectory = await showSelectDirectoryPicker(context);
if (selectedDirectory != null) {
selectedDirectory += path.separator;
final customThemeDir = path.join(await Provider.of<FlwtchState>(context, listen: false).cwtch.getCwtchDir(), custom_themes_subdir);
importThemeCheck(context, settings, customThemeDir, selectedDirectory);
} else {
// User canceled the picker
}
},
//onChanged: widget.onSave,
icon: Icon(Icons.folder),
//tooltip: widget.tooltip,
)))),
SwitchListTile(
title: Text(AppLocalizations.of(context)!.settingsThemeImages),
subtitle: Text(AppLocalizations.of(context)!.settingsThemeImagesDescription),
value: settings.themeImages,
onChanged: (bool value) {
settings.themeImages = value; // Save Settings...
saveSettings(context);
},
activeTrackColor: settings.theme.defaultButtonColor,
inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
secondary: Icon(Icons.image, color: settings.current().mainTextColor),
),
ListTile(
title: Text(AppLocalizations.of(context)!.settingUIColumnPortrait),
leading: Icon(Icons.table_chart, color: settings.current().mainTextColor),
trailing: Container(
width: MediaQuery.of(context).size.width / 4,
child: DropdownButton(
isExpanded: true,
value: settings.uiColumnModePortrait.toString(),
onChanged: (String? newValue) {
settings.uiColumnModePortrait = Settings.uiColumnModeFromString(newValue!);
saveSettings(context);
},
items: Settings.uiColumnModeOptions(false).map<DropdownMenuItem<String>>((DualpaneMode value) {
return DropdownMenuItem<String>(
value: value.toString(),
child: Text(Settings.uiColumnModeToString(value, context), style: settings.scaleFonts(defaultDropDownMenuItemTextStyle)),
);
}).toList()))),
ListTile(
title: Text(
AppLocalizations.of(context)!.settingUIColumnLandscape,
textWidthBasis: TextWidthBasis.longestLine,
softWrap: true,
),
leading: Icon(Icons.stay_primary_landscape, color: settings.current().mainTextColor),
trailing: Container(
width: MediaQuery.of(context).size.width / 4,
child: Container(
width: MediaQuery.of(context).size.width / 4,
child: DropdownButton(
isExpanded: true,
value: settings.uiColumnModeLandscape.toString(),
onChanged: (String? newValue) {
settings.uiColumnModeLandscape = Settings.uiColumnModeFromString(newValue!);
saveSettings(context);
},
items: Settings.uiColumnModeOptions(true).map<DropdownMenuItem<String>>((DualpaneMode value) {
return DropdownMenuItem<String>(
value: value.toString(),
child: Text(Settings.uiColumnModeToString(value, context), overflow: TextOverflow.ellipsis, style: settings.scaleFonts(defaultDropDownMenuItemTextStyle)),
);
}).toList())))),
ListTile(
title: Text(AppLocalizations.of(context)!.defaultScalingText),
subtitle: Text(AppLocalizations.of(context)!.fontScalingDescription),
trailing: Container(
width: MediaQuery.of(context).size.width / 4,
child: Slider(
onChanged: (double value) {
settings.fontScaling = value;
// Save Settings...
saveSettings(context);
EnvironmentConfig.debugLog("Font Scaling: $value");
},
min: 0.5,
divisions: 12,
max: 2.0,
label: '${settings.fontScaling * 100}%',
activeColor: settings.current().defaultButtonColor,
thumbColor: settings.current().mainTextColor,
overlayColor: MaterialStateProperty.all(settings.current().mainTextColor),
inactiveColor: settings.theme.defaultButtonDisabledColor,
value: settings.fontScaling)),
leading: Icon(Icons.format_size, color: settings.current().mainTextColor),
),
SwitchListTile(
title: Text(AppLocalizations.of(context)!.streamerModeLabel),
subtitle: Text(AppLocalizations.of(context)!.descriptionStreamerMode),
value: settings.streamerMode,
onChanged: (bool value) {
settings.setStreamerMode(value);
// Save Settings...
saveSettings(context);
},
activeTrackColor: settings.theme.defaultButtonColor,
inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
secondary: Icon(CwtchIcons.streamer_bunnymask, color: settings.current().mainTextColor),
),
SwitchListTile(
title: Text(AppLocalizations.of(context)!.formattingExperiment),
subtitle: Text(AppLocalizations.of(context)!.messageFormattingDescription),
value: settings.isExperimentEnabled(FormattingExperiment),
onChanged: (bool value) {
if (value) {
settings.enableExperiment(FormattingExperiment);
} else {
settings.disableExperiment(FormattingExperiment);
}
saveSettings(context);
},
activeTrackColor: settings.theme.defaultButtonColor,
inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
secondary: Icon(Icons.text_fields, color: settings.current().mainTextColor),
),
])))));
});
});
}
/// A slightly verbose way to extract the full language name from
/// an individual language code. There might be a more efficient way of doing this.
String getLanguageFull(context, String languageCode, String? countryCode) {
if (languageCode == "en") {
return AppLocalizations.of(context)!.localeEn;
}
if (languageCode == "es") {
return AppLocalizations.of(context)!.localeEs;
}
if (languageCode == "fr") {
return AppLocalizations.of(context)!.localeFr;
}
if (languageCode == "pt" && countryCode == "BR") {
return AppLocalizations.of(context)!.localePtBr;
}
if (languageCode == "pt") {
return AppLocalizations.of(context)!.localePt;
}
if (languageCode == "de") {
return AppLocalizations.of(context)!.localeDe;
}
if (languageCode == "el") {
return AppLocalizations.of(context)!.localeEl;
}
if (languageCode == "it") {
return AppLocalizations.of(context)!.localeIt;
}
if (languageCode == "no") {
return AppLocalizations.of(context)!.localeNo;
}
if (languageCode == "pl") {
return AppLocalizations.of(context)!.localePl;
}
if (languageCode == "lb") {
return AppLocalizations.of(context)!.localeLb;
}
if (languageCode == "ru") {
return AppLocalizations.of(context)!.localeRU;
}
if (languageCode == "ro") {
return AppLocalizations.of(context)!.localeRo;
}
if (languageCode == "cy") {
return AppLocalizations.of(context)!.localeCy;
}
if (languageCode == "da") {
return AppLocalizations.of(context)!.localeDa;
}
if (languageCode == "tr") {
return AppLocalizations.of(context)!.localeTr;
}
if (languageCode == "nl") {
return AppLocalizations.of(context)!.localeNl;
}
if (languageCode == "sk") {
return AppLocalizations.of(context)!.localeSk;
}
if (languageCode == "ko") {
return AppLocalizations.of(context)!.localeKo;
}
if (languageCode == "ja") {
return AppLocalizations.of(context)!.localeJa;
}
if (languageCode == "sv") {
return AppLocalizations.of(context)!.localeSv;
}
if (languageCode == "sw") {
return AppLocalizations.of(context)!.localeSw;
}
if (languageCode == "uk") {
return AppLocalizations.of(context)!.localeUk;
}
if (languageCode == "uz") {
return AppLocalizations.of(context)!.localeUzbek;
}
return languageCode;
}
/// Since we don't seem to able to dynamically pull translations, this function maps themes to their names
String getThemeName(context, Settings settings, String theme) {
switch (theme) {
case cwtch_theme:
return AppLocalizations.of(context)!.themeNameCwtch;
case "ghost":
return AppLocalizations.of(context)!.themeNameGhost;
case "mermaid":
return AppLocalizations.of(context)!.themeNameMermaid;
case "midnight":
return AppLocalizations.of(context)!.themeNameMidnight;
case "neon1":
return AppLocalizations.of(context)!.themeNameNeon1;
case "neon2":
return AppLocalizations.of(context)!.themeNameNeon2;
case "pumpkin":
return AppLocalizations.of(context)!.themeNamePumpkin;
case "vampire":
return AppLocalizations.of(context)!.themeNameVampire;
case "witch":
return AppLocalizations.of(context)!.themeNameWitch;
case "juniper":
return "Juniper"; // Juniper is a noun, and doesn't get subject to translation...
}
return settings.themeloader.themes[theme]?[mode_light]?.theme ?? settings.themeloader.themes[theme]?[mode_dark]?.theme ?? theme;
}
void importThemeCheck(BuildContext context, Settings settings, String themesDir, String newThemeDirectory) async {
// check is theme
final srcDir = Directory(newThemeDirectory);
String themeName = path.basename(newThemeDirectory);
File themeFile = File(path.join(newThemeDirectory, "theme.yml"));
if (!themeFile.existsSync()) {
// error, isnt valid theme, no .yml theme file found
SnackBar err = SnackBar(content: Text(AppLocalizations.of(context)!.settingsThemeErrorInvalid.replaceAll("\$themeName", themeName)));
ScaffoldMessenger.of(context).showSnackBar(err);
return;
}
Directory targetDir = Directory(path.join(themesDir, themeName));
// check if exists
if (settings.themeloader.themes.containsKey(themeName) || targetDir.existsSync()) {
_modalConfirmOverwriteCustomTheme(srcDir, targetDir, themesDir, themeName, settings);
} else {
importTheme(srcDir, targetDir, themesDir, themeName, settings);
}
}
void importTheme(Directory srcDir, Directory targetDir, String themesDir, String themeName, Settings settings) async {
if (!targetDir.existsSync()) {
targetDir = await targetDir.create();
}
// importTheme(newVal)
await for (var entity in srcDir.list(recursive: false)) {
if (entity is File) {
entity.copySync(path.join(targetDir.path, path.basename(entity.path)));
}
}
var data = await loadFileYamlTheme(path.join(targetDir.path, "theme.yml"), targetDir.path);
if (data != null) {
settings.themeloader.themes[themeName] = data;
}
if (settings.current().theme == themeName) {
settings.setTheme(themeName, settings.current().mode);
}
}
void _modalConfirmOverwriteCustomTheme(Directory srcDir, Directory targetDir, String themesDir, String themeName, Settings settings) {
showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
builder: (BuildContext context) {
return Padding(
padding: MediaQuery.of(context).viewInsets,
child: RepaintBoundary(
child: Container(
height: Platform.isAndroid ? 250 : 200, // bespoke value courtesy of the [TextField] docs
child: Center(
child: Padding(
padding: EdgeInsets.all(10.0),
child: Column(mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: <Widget>[
Text(AppLocalizations.of(context)!.settingThemeOverwriteQuestion.replaceAll("\$themeName", themeName)),
SizedBox(
height: 20,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Spacer(),
Expanded(
child: ElevatedButton(
child: Text(AppLocalizations.of(context)!.settingThemeOverwriteConfirm, semanticsLabel: AppLocalizations.of(context)!.settingThemeOverwriteConfirm),
onPressed: () {
importTheme(srcDir, targetDir, themesDir, themeName, settings);
Navigator.pop(context);
},
)),
SizedBox(
width: 20,
),
Expanded(
child: ElevatedButton(
child: Text(AppLocalizations.of(context)!.cancel, semanticsLabel: AppLocalizations.of(context)!.cancel),
onPressed: () {
Navigator.pop(context);
},
)),
Spacer(),
],
)
]))))));
});
}
}