From 04985ee7eb56738c11c6b2512211b05da06bfb35 Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Sat, 6 Jan 2024 11:39:07 -0800 Subject: [PATCH] split settings panes into seperate files --- lib/views/globalsettingsaboutview.dart | 197 ++++ lib/views/globalsettingsappearanceview.dart | 420 +++++++ lib/views/globalsettingsbehaviourview.dart | 242 ++++ lib/views/globalsettingsexperimentsview.dart | 403 +++++++ lib/views/globalsettingsview.dart | 1076 +----------------- 5 files changed, 1270 insertions(+), 1068 deletions(-) create mode 100644 lib/views/globalsettingsaboutview.dart create mode 100644 lib/views/globalsettingsappearanceview.dart create mode 100644 lib/views/globalsettingsbehaviourview.dart create mode 100644 lib/views/globalsettingsexperimentsview.dart diff --git a/lib/views/globalsettingsaboutview.dart b/lib/views/globalsettingsaboutview.dart new file mode 100644 index 00000000..215a819c --- /dev/null +++ b/lib/views/globalsettingsaboutview.dart @@ -0,0 +1,197 @@ +import 'dart:collection'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import '../config.dart'; +import '../cwtch_icons_icons.dart'; +import '../main.dart'; +import '../settings.dart'; +import '../themes/opaque.dart'; +import 'globalsettingsview.dart'; + +class GlobalSettingsAboutView extends StatefulWidget { + @override + _GlobalSettingsAboutViewState createState() => _GlobalSettingsAboutViewState(); +} + +class _GlobalSettingsAboutViewState extends State { + + ScrollController settingsListScrollController = ScrollController(); + + Widget build(BuildContext context) { + return Consumer(builder: (ccontext, settings, child) { + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints viewportConstraints) { + var appIcon = Icon(Icons.info, color: settings + .current() + .mainTextColor); + return Scrollbar( + key: Key("AboutSettingsView"), + trackVisibility: true, + controller: settingsListScrollController, + child: SingleChildScrollView( + clipBehavior: Clip.antiAlias, + controller: settingsListScrollController, + padding: EdgeInsets.symmetric(vertical: 0, horizontal: 20), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: Column(children: [ + AboutListTile( + icon: appIcon, + applicationIcon: Padding( + padding: EdgeInsets.all(5), + child: Icon(CwtchIcons.cwtch_knott)), + applicationName: "Cwtch UI", + applicationLegalese: + '\u{a9} 2021-2023 Open Privacy Research Society', + aboutBoxChildren: [ + Padding( + padding: EdgeInsets.fromLTRB( + 24.0 + 10.0 + (appIcon.size ?? 24.0), + 16.0, + 0.0, + 0.0), + // About has 24 padding (ln 389) and there appears to be another 10 of padding in the widget + child: SelectableText( + AppLocalizations.of(context)! + .versionBuilddate + .replaceAll( + "%1", EnvironmentConfig.BUILD_VER) + .replaceAll("%2", + EnvironmentConfig.BUILD_DATE)), + ) + ]), + SwitchListTile( + // TODO: Translate, Remove, OR Hide Prior to Release + title: Text("Show Performance Overlay"), + subtitle: + Text("Display an overlay graph of render time."), + value: settings.profileMode, + onChanged: (bool value) { + setState(() { + if (value) { + settings.profileMode = value; + } else { + settings.profileMode = value; + } + }); + }, + activeTrackColor: + settings.theme.defaultButtonActiveColor, + inactiveTrackColor: + settings.theme.defaultButtonDisabledColor, + secondary: Icon(Icons.bar_chart, + color: settings + .current() + .mainTextColor), + ), + Visibility( + visible: + EnvironmentConfig.BUILD_VER == dev_version && + !Platform.isAndroid, + child: SwitchListTile( + title: Text("Show Semantic Debugger"), + subtitle: + Text("Show Accessibility Debugging View"), + value: settings.useSemanticDebugger, + onChanged: (bool value) { + if (value) { + settings.useSemanticDebugger = value; + } else { + settings.useSemanticDebugger = value; + } + saveSettings(context); + }, + activeTrackColor: + settings.theme.defaultButtonActiveColor, + inactiveTrackColor: + settings.theme.defaultButtonDisabledColor, + secondary: Icon(Icons.settings_accessibility, + color: settings + .current() + .mainTextColor), + )), + Visibility( + visible: EnvironmentConfig.BUILD_VER == dev_version && + !Platform.isAndroid, + child: FutureBuilder( + future: + EnvironmentConfig.BUILD_VER != dev_version || + Platform.isAndroid + ? null + : Provider + .of(context) + .cwtch + .GetDebugInfo(), + builder: (context, snapshot) { + if (snapshot.hasData) { + return Column( + children: [ + Text("libCwtch Debug Info: " + + snapshot.data.toString()), + Text("Message Cache Size (Mb): " + + (Provider + .of(context) + .profs + .cacheMemUsage() / + (1024 * 1024)) + .toString()) + ], + ); + } else { + return Container(); + } + }, + ), + ), + Visibility( + visible: EnvironmentConfig.BUILD_VER == dev_version, + child: FutureBuilder( + future: Provider + .of(context) + .cwtch + .PlatformChannelInfo(), + builder: (context, snapshot) { + if (snapshot.hasData) { + HashMap data = snapshot.data + as HashMap; + return getPlatformInfo(settings, data); + } + return Container(); + })) + ])))); + }); + }); + } + + getPlatformInfo(settings, HashMap platformChannelInfo) { + var sortedKeys = platformChannelInfo.keys.toList(); + sortedKeys.sort(); + var widgets = List.empty(growable: true); + sortedKeys.forEach((element) { + widgets.add(ListTile( + leading: Icon(Icons.android, color: settings.current().mainTextColor), + title: Text(element), + subtitle: Text(platformChannelInfo[element]!), + )); + }); + return Column( + children: widgets, + ); + } + + // TODO: deprecated ? + /// Construct a version string from Package Info + String constructVersionString(PackageInfo pinfo) { + if (pinfo == null) { + return ""; + } + return pinfo.version + "." + pinfo.buildNumber; + } +} \ No newline at end of file diff --git a/lib/views/globalsettingsappearanceview.dart b/lib/views/globalsettingsappearanceview.dart new file mode 100644 index 00000000..54882d74 --- /dev/null +++ b/lib/views/globalsettingsappearanceview.dart @@ -0,0 +1,420 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import '../config.dart'; +import '../cwtch_icons_icons.dart'; +import '../settings.dart'; +import '../themes/cwtch.dart'; +import '../themes/opaque.dart'; +import 'globalsettingsview.dart'; + +class GlobalSettingsAppearanceView extends StatefulWidget { + @override + _GlobalSettingsAppearanceViewState createState() => _GlobalSettingsAppearanceViewState(); +} + +class _GlobalSettingsAppearanceViewState extends State { + + ScrollController settingsListScrollController = ScrollController(); + + Widget build(BuildContext context) { + return Consumer(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, + padding: EdgeInsets.symmetric(vertical: 0, horizontal: 20), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + 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(context) + .locale + .toString(), + onChanged: (String? newValue) { + setState(() { + EnvironmentConfig.debugLog( + "setting language: $newValue"); + settings.switchLocaleByCode(newValue!); + saveSettings(context); + }); + }, + items: AppLocalizations.supportedLocales + .map>( + (Locale value) { + return DropdownMenuItem( + 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( + key: Key("DropdownTheme"), + isExpanded: true, + value: Provider + .of(context) + .theme + .theme, + onChanged: (String? newValue) { + setState(() { + settings.setTheme( + newValue!, settings.theme.mode); + saveSettings(context); + }); + }, + items: themes.keys + .map>( + (String themeId) { + return DropdownMenuItem( + value: themeId, + child: Text( + getThemeName(context, themeId), + style: settings.scaleFonts( + defaultDropDownMenuItemTextStyle)), //"ddi_$themeId", key: Key("ddi_$themeId")), + ); + }).toList())), + leading: Icon(Icons.palette, + 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>( + (DualpaneMode value) { + return DropdownMenuItem( + 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>( + (DualpaneMode value) { + return DropdownMenuItem( + 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.defaultButtonActiveColor, + 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, 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 theme; + } +} diff --git a/lib/views/globalsettingsbehaviourview.dart b/lib/views/globalsettingsbehaviourview.dart new file mode 100644 index 00000000..31116d86 --- /dev/null +++ b/lib/views/globalsettingsbehaviourview.dart @@ -0,0 +1,242 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import '../config.dart'; +import '../cwtch_icons_icons.dart'; +import '../settings.dart'; +import '../themes/opaque.dart'; +import 'globalsettingsview.dart'; + +class GlobalSettingsBehaviourView extends StatefulWidget { + @override + _GlobalSettingsBehaviourViewState createState() => _GlobalSettingsBehaviourViewState(); +} + +class _GlobalSettingsBehaviourViewState extends State { + static const androidSettingsChannel = const MethodChannel('androidSettings'); + static const androidSettingsChangeChannel = const MethodChannel('androidSettingsChanged'); + + + ScrollController settingsListScrollController = ScrollController(); + bool powerExempt = false; + + @override + void initState() { + super.initState(); + androidSettingsChangeChannel.setMethodCallHandler(handleSettingsChanged); + + if (Platform.isAndroid) { + isBatteryExempt().then((value) => setState(() { + powerExempt = value; + })); + } else { + powerExempt = false; + } + } + + // Handler on method channel for MainActivity/onActivityResult to report the user choice when we ask for power exemption + Future handleSettingsChanged(MethodCall call) async { + if (call.method == "powerExemptionChange") { + if (call.arguments) { + setState(() { + powerExempt = true; + }); + } + } + } + //* Android Only Requests + + Future isBatteryExempt() async { + return await androidSettingsChannel.invokeMethod('isBatteryExempt', {}) ?? + false; + } + + Future requestBatteryExemption() async { + await androidSettingsChannel.invokeMethod('requestBatteryExemption', {}); + return Future.value(); + } + + //* End Android Only Requests + + + Widget build(BuildContext context) { + return Consumer(builder: (ccontext, settings, child) { + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints viewportConstraints) { + return Scrollbar( + key: Key("BehaviourSettingsView"), + trackVisibility: true, + controller: settingsListScrollController, + child: SingleChildScrollView( + clipBehavior: Clip.antiAlias, + controller: settingsListScrollController, + padding: EdgeInsets.symmetric(vertical: 0, horizontal: 20), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: Column(children: [ + Visibility( + visible: Platform.isAndroid, + child: SwitchListTile( + title: Text(AppLocalizations.of(context)! + .settingAndroidPowerExemption), + subtitle: Text(AppLocalizations.of(context)! + .settingAndroidPowerExemptionDescription), + value: powerExempt, + onChanged: (bool value) { + if (value) { + requestBatteryExemption(); + } else { + // We can't ask for it to be turned off, show an informational popup + showBatteryDialog(context); + } + }, + activeTrackColor: settings.theme.defaultButtonColor, + inactiveTrackColor: + settings.theme.defaultButtonDisabledColor, + secondary: Icon(Icons.power, + color: settings.current().mainTextColor), + ), + ), + ListTile( + title: Text(AppLocalizations.of(context)! + .notificationPolicySettingLabel), + subtitle: Text(AppLocalizations.of(context)! + .notificationPolicySettingDescription), + trailing: Container( + width: MediaQuery.of(context).size.width / 4, + child: DropdownButton( + isExpanded: true, + value: settings.notificationPolicy, + onChanged: (NotificationPolicy? newValue) { + settings.notificationPolicy = newValue!; + saveSettings(context); + }, + items: NotificationPolicy.values.map< + DropdownMenuItem>( + (NotificationPolicy value) { + return DropdownMenuItem( + value: value, + child: Text( + Settings.notificationPolicyToString( + value, context), + overflow: TextOverflow.ellipsis, + style: settings.scaleFonts( + defaultDropDownMenuItemTextStyle)), + ); + }).toList())), + leading: Icon(CwtchIcons.chat_bubble_empty_24px, + color: settings.current().mainTextColor), + ), + ListTile( + title: Text(AppLocalizations.of(context)! + .notificationContentSettingLabel), + subtitle: Text(AppLocalizations.of(context)! + .notificationContentSettingDescription), + trailing: Container( + width: MediaQuery.of(context).size.width / 4, + child: DropdownButton( + isExpanded: true, + value: settings.notificationContent, + onChanged: (NotificationContent? newValue) { + settings.notificationContent = newValue!; + saveSettings(context); + }, + items: NotificationContent.values.map< + DropdownMenuItem< + NotificationContent>>( + (NotificationContent value) { + return DropdownMenuItem< + NotificationContent>( + value: value, + child: Text( + Settings.notificationContentToString( + value, context), + overflow: TextOverflow.ellipsis, + style: settings.scaleFonts( + defaultDropDownMenuItemTextStyle)), + ); + }).toList())), + leading: Icon(CwtchIcons.chat_bubble_empty_24px, + color: settings.current().mainTextColor), + ), + SwitchListTile( + title: Text( + AppLocalizations.of(context)!.blockUnknownLabel), + subtitle: Text(AppLocalizations.of(context)! + .descriptionBlockUnknownConnections), + value: settings.blockUnknownConnections, + onChanged: (bool value) { + if (value) { + settings.forbidUnknownConnections(); + } else { + settings.allowUnknownConnections(); + } + + // Save Settings... + saveSettings(context); + }, + activeTrackColor: settings.theme.defaultButtonColor, + inactiveTrackColor: + settings.theme.defaultButtonDisabledColor, + secondary: Icon(CwtchIcons.block_unknown, + color: settings.current().mainTextColor), + ), + SwitchListTile( + title: Text(AppLocalizations.of(context)! + .defaultPreserveHistorySetting), + subtitle: Text(AppLocalizations.of(context)! + .preserveHistorySettingDescription), + value: settings.preserveHistoryByDefault, + onChanged: (bool value) { + if (value) { + settings.setPreserveHistoryDefault(); + } else { + settings.setDeleteHistoryDefault(); + } + + // Save Settings... + saveSettings(context); + }, + activeTrackColor: settings.theme.defaultButtonColor, + inactiveTrackColor: + settings.theme.defaultButtonDisabledColor, + secondary: Icon(CwtchIcons.peer_history, + color: settings.current().mainTextColor), + ), + ])))); + }); + }); + } + + showBatteryDialog(BuildContext context) { + Widget okButton = ElevatedButton( + child: Text(AppLocalizations.of(context)!.okButton), + onPressed: () { + Navigator.of(context).pop(); + }, + ); + + // set up the AlertDialog + AlertDialog alert = AlertDialog( + title: + Text(AppLocalizations.of(context)!.settingsAndroidPowerReenablePopup), + actions: [ + okButton, + ], + ); + + // show the dialog + showDialog( + context: context, + builder: (BuildContext context) { + return alert; + }, + ); + } +} \ No newline at end of file diff --git a/lib/views/globalsettingsexperimentsview.dart b/lib/views/globalsettingsexperimentsview.dart new file mode 100644 index 00000000..a8e58712 --- /dev/null +++ b/lib/views/globalsettingsexperimentsview.dart @@ -0,0 +1,403 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import '../config.dart'; +import '../cwtch_icons_icons.dart'; +import '../main.dart'; +import '../models/servers.dart'; +import '../settings.dart'; +import '../themes/opaque.dart'; +import '../widgets/folderpicker.dart'; +import 'globalsettingsview.dart'; + +class GlobalSettingsExperimentsView extends StatefulWidget { + @override + _GlobalSettingsExperimentsViewState createState() => _GlobalSettingsExperimentsViewState(); +} + +class _GlobalSettingsExperimentsViewState extends State { + + ScrollController settingsListScrollController = ScrollController(); + + Widget build(BuildContext context) { + return Consumer(builder: (ccontext, settings, child) { + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints viewportConstraints) { + return Scrollbar( + key: Key("ExperimentsSettingsView"), + trackVisibility: true, + controller: settingsListScrollController, + child: SingleChildScrollView( + clipBehavior: Clip.antiAlias, + controller: settingsListScrollController, + padding: EdgeInsets.symmetric(vertical: 0, horizontal: 20), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: Column(children: [ + SwitchListTile( + title: Text( + AppLocalizations.of(context)!.experimentsEnabled), + subtitle: Text(AppLocalizations.of(context)! + .descriptionExperiments), + value: settings.experimentsEnabled, + onChanged: (bool value) { + if (value) { + settings.enableExperiments(); + } else { + settings.disableExperiments(); + } + // Save Settings... + saveSettings(context); + }, + activeTrackColor: settings.theme.defaultButtonColor, + inactiveTrackColor: + settings.theme.defaultButtonDisabledColor, + secondary: Icon(CwtchIcons.enable_experiments, + color: settings + .current() + .mainTextColor), + ), + Visibility( + visible: settings.experimentsEnabled, + child: Column( + children: [ + SwitchListTile( + title: Text(AppLocalizations.of(context)! + .enableGroups), + subtitle: Text(AppLocalizations.of(context)! + .descriptionExperimentsGroups), + value: settings.isExperimentEnabled( + TapirGroupsExperiment), + onChanged: (bool value) { + if (value) { + settings.enableExperiment( + TapirGroupsExperiment); + } else { + settings.disableExperiment( + TapirGroupsExperiment); + } + // Save Settings... + saveSettings(context); + }, + activeTrackColor: + settings.theme.defaultButtonColor, + inactiveTrackColor: + settings.theme.defaultButtonDisabledColor, + secondary: Icon(CwtchIcons.enable_groups, + color: settings + .current() + .mainTextColor), + ), + Visibility( + visible: + !Platform.isAndroid && !Platform.isIOS, + child: SwitchListTile( + title: Text(AppLocalizations.of(context)! + .settingServers), + subtitle: Provider + .of( + context, + listen: false) + .cwtch + .IsServersCompiled() + ? Text(AppLocalizations.of(context)! + .settingServersDescription) + : Text( + "This version of Cwtch has been compiled without support for the server hosting experiment."), + value: Provider + .of(context, + listen: false) + .cwtch + .IsServersCompiled() && + settings.isExperimentEnabled( + ServerManagementExperiment), + onChanged: Provider + .of( + context, + listen: false) + .cwtch + .IsServersCompiled() + ? (bool value) { + Provider.of( + context, + listen: false) + .clear(); + if (value) { + settings.enableExperiment( + ServerManagementExperiment); + } else { + settings.disableExperiment( + ServerManagementExperiment); + } + // Save Settings... + saveSettings(context); + } + : null, + activeTrackColor: + settings.theme.defaultButtonColor, + inactiveTrackColor: settings + .theme.defaultButtonDisabledColor, + inactiveThumbColor: settings + .theme.defaultButtonDisabledColor, + secondary: Icon(CwtchIcons.dns_24px), + )), + SwitchListTile( + title: Text(AppLocalizations.of(context)! + .settingFileSharing), + subtitle: Text(AppLocalizations.of(context)! + .descriptionFileSharing), + value: settings.isExperimentEnabled( + FileSharingExperiment), + onChanged: (bool value) { + if (value) { + if (checkDownloadDirectory( + context, settings)) { + settings.enableExperiment( + FileSharingExperiment); + } else { + settings.enableExperiment( + FileSharingExperiment); + settings.disableExperiment( + ImagePreviewsExperiment); + } + } else { + settings.disableExperiment( + FileSharingExperiment); + settings.disableExperiment( + ImagePreviewsExperiment); + } + saveSettings(context); + }, + activeTrackColor: + settings.theme.defaultButtonColor, + inactiveTrackColor: + settings.theme.defaultButtonDisabledColor, + secondary: Icon(CwtchIcons.attached_file_3, + color: settings + .current() + .mainTextColor), + ), + Visibility( + visible: settings.isExperimentEnabled( + FileSharingExperiment), + child: Column(children: [ + SwitchListTile( + title: Text(AppLocalizations.of(context)! + .settingImagePreviews), + subtitle: Text( + AppLocalizations.of(context)! + .settingImagePreviewsDescription), + value: settings.isExperimentEnabled( + ImagePreviewsExperiment), + onChanged: (bool value) { + if (value) { + if (checkDownloadDirectory( + context, settings)) { + settings.enableExperiment( + ImagePreviewsExperiment); + } else { + settings.disableExperiment( + ImagePreviewsExperiment); + } + } else { + settings.disableExperiment( + ImagePreviewsExperiment); + } + saveSettings(context); + }, + activeTrackColor: settings + .theme.defaultButtonActiveColor, + inactiveTrackColor: settings + .theme.defaultButtonDisabledColor, + secondary: Icon(Icons.photo, + color: + settings + .current() + .mainTextColor), + ), + Visibility( + visible: settings.isExperimentEnabled( + ImagePreviewsExperiment) && + !Platform.isAndroid, + child: CwtchFolderPicker( + testKey: Key("DownloadFolderPicker"), + label: AppLocalizations.of(context)! + .settingDownloadFolder, + initialValue: settings.downloadPath, + textStyle: settings.scaleFonts( + defaultDropDownMenuItemTextStyle), + description: AppLocalizations.of( + context)! + .fileSharingSettingsDownloadFolderDescription, + tooltip: AppLocalizations.of(context)! + .fileSharingSettingsDownloadFolderTooltip, + onSave: (newVal) { + settings.downloadPath = newVal; + saveSettings(context); + }, + ), + ), + ]), + ), + SwitchListTile( + title: Text(AppLocalizations.of(context)! + .blodeuweddExperimentEnable), + subtitle: Provider + .of(context, + listen: false) + .cwtch + .IsBlodeuweddSupported() + ? Text(AppLocalizations.of(context)! + .blodeuweddDescription) + : Text(AppLocalizations.of(context)! + .blodeuweddNotSupported), + value: Provider + .of(context, + listen: false) + .cwtch + .IsBlodeuweddSupported() && + settings.isExperimentEnabled( + BlodeuweddExperiment), + onChanged: Provider + .of(context, + listen: false) + .cwtch + .IsBlodeuweddSupported() + ? (bool value) { + if (value) { + settings.enableExperiment( + BlodeuweddExperiment); + } else { + settings.disableExperiment( + BlodeuweddExperiment); + } + saveSettings(context); + } + : null, + activeTrackColor: + settings.theme.defaultButtonColor, + inactiveTrackColor: + settings.theme.defaultButtonDisabledColor, + inactiveThumbColor: + settings.theme.defaultButtonDisabledColor, + secondary: Icon(Icons.assistant, + color: settings + .current() + .mainTextColor), + ), + Visibility( + visible: Provider + .of(context, + listen: false) + .cwtch + .IsBlodeuweddSupported() && + settings.isExperimentEnabled( + BlodeuweddExperiment), + child: CwtchFolderPicker( + testKey: Key("DownloadFolderPicker"), + label: AppLocalizations.of(context)! + .settingDownloadFolder, + initialValue: settings.blodeuweddPath, + description: AppLocalizations.of(context)! + .blodeuweddPath, + tooltip: AppLocalizations.of(context)! + .blodeuweddPath, + onSave: (newVal) { + settings.blodeuweddPath = newVal; + saveSettings(context); + }, + ), + ), + ], + )), + Visibility( + visible: settings.experimentsEnabled, + child: SwitchListTile( + title: Text(AppLocalizations.of(context)! + .enableExperimentClickableLinks), + subtitle: Text(AppLocalizations.of(context)! + .experimentClickableLinksDescription), + value: settings.isExperimentEnabled( + ClickableLinksExperiment), + onChanged: (bool value) { + if (value) { + settings.enableExperiment( + ClickableLinksExperiment); + } else { + settings.disableExperiment( + ClickableLinksExperiment); + } + saveSettings(context); + }, + activeTrackColor: + settings.theme.defaultButtonActiveColor, + inactiveTrackColor: + settings.theme.defaultButtonDisabledColor, + secondary: Icon(Icons.link, + color: settings + .current() + .mainTextColor), + )), + Visibility( + visible: settings.experimentsEnabled, + child: SwitchListTile( + title: Text(AppLocalizations.of(context)! + .enableExperimentQRCode), + subtitle: Text(AppLocalizations.of(context)! + .experimentQRCodeDescription), + value: settings + .isExperimentEnabled(QRCodeExperiment), + onChanged: (bool value) { + if (value) { + settings.enableExperiment(QRCodeExperiment); + } else { + settings.disableExperiment(QRCodeExperiment); + } + saveSettings(context); + }, + activeTrackColor: + settings.theme.defaultButtonActiveColor, + inactiveTrackColor: + settings.theme.defaultButtonDisabledColor, + secondary: Icon(Icons.qr_code, + color: settings + .current() + .mainTextColor), + )), + ])))); + }); + }); + } + + bool checkDownloadDirectory(context, settings) { + bool showError = false; + if (settings.downloadPath != "") { + } else { + // check if the default download path exists + var path = Provider.of(context, listen: false) + .cwtch + .defaultDownloadPath(); + if (path != null) { + settings.downloadPath = path; + } else { + showError = true; + } + } + + if (!showError && Directory(settings.downloadPath).existsSync()) { + return true; + } else { + final snackBar = SnackBar( + content: Text( + AppLocalizations.of(context)!.errorDownloadDirectoryDoesNotExist), + ); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + return false; + } + } +} \ No newline at end of file diff --git a/lib/views/globalsettingsview.dart b/lib/views/globalsettingsview.dart index b7f56779..6bb52493 100644 --- a/lib/views/globalsettingsview.dart +++ b/lib/views/globalsettingsview.dart @@ -3,6 +3,10 @@ import 'dart:convert'; import 'dart:io'; import 'package:cwtch/cwtch_icons_icons.dart'; import 'package:cwtch/models/servers.dart'; +import 'package:cwtch/views/globalsettingsaboutview.dart'; +import 'package:cwtch/views/globalsettingsappearanceview.dart'; +import 'package:cwtch/views/globalsettingsbehaviourview.dart'; +import 'package:cwtch/views/globalsettingsexperimentsview.dart'; import 'package:cwtch/widgets/folderpicker.dart'; import 'package:cwtch/themes/cwtch.dart'; import 'package:cwtch/themes/opaque.dart'; @@ -23,10 +27,6 @@ class GlobalSettingsView extends StatefulWidget { } class _GlobalSettingsViewState extends State { - static const androidSettingsChannel = const MethodChannel('androidSettings'); - static const androidSettingsChangeChannel = - const MethodChannel('androidSettingsChanged'); - bool powerExempt = false; ScrollController settingsListScrollController = ScrollController(); @@ -35,44 +35,6 @@ class _GlobalSettingsViewState extends State { super.dispose(); } - @override - void initState() { - super.initState(); - androidSettingsChangeChannel.setMethodCallHandler(handleSettingsChanged); - - if (Platform.isAndroid) { - isBatteryExempt().then((value) => setState(() { - powerExempt = value; - })); - } else { - powerExempt = false; - } - } - - // Handler on method channel for MainActivity/onActivityResult to report the user choice when we ask for power exemption - Future handleSettingsChanged(MethodCall call) async { - if (call.method == "powerExemptionChange") { - if (call.arguments) { - setState(() { - powerExempt = true; - }); - } - } - } - - //* Android Only Requests - - Future isBatteryExempt() async { - return await androidSettingsChannel.invokeMethod('isBatteryExempt', {}) ?? - false; - } - - Future requestBatteryExemption() async { - await androidSettingsChannel.invokeMethod('requestBatteryExemption', {}); - return Future.value(); - } - - //* End Android Only Requests @override Widget build(BuildContext context) { @@ -111,1040 +73,18 @@ class _GlobalSettingsViewState extends State { return Consumer(builder: (ccontext, settings, child) { return LayoutBuilder( builder: (BuildContext context, BoxConstraints viewportConstraints) { - var appIcon = Icon(Icons.info, color: settings.current().mainTextColor); return TabBarView(children: [ - // ***** Appearance - Scrollbar( - key: Key("AppearanceSettingsView"), - trackVisibility: true, - controller: settingsListScrollController, - child: SingleChildScrollView( - clipBehavior: Clip.antiAlias, - controller: settingsListScrollController, - padding: EdgeInsets.symmetric(vertical: 0, horizontal: 20), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: viewportConstraints.maxHeight, - ), - 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(context) - .locale - .toString(), - onChanged: (String? newValue) { - setState(() { - EnvironmentConfig.debugLog( - "setting language: $newValue"); - settings.switchLocaleByCode(newValue!); - saveSettings(context); - }); - }, - items: AppLocalizations.supportedLocales - .map>( - (Locale value) { - return DropdownMenuItem( - 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); - } + GlobalSettingsAppearanceView(), - // 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( - key: Key("DropdownTheme"), - isExpanded: true, - value: Provider.of(context) - .theme - .theme, - onChanged: (String? newValue) { - setState(() { - settings.setTheme( - newValue!, settings.theme.mode); - saveSettings(context); - }); - }, - items: themes.keys - .map>( - (String themeId) { - return DropdownMenuItem( - value: themeId, - child: Text( - getThemeName(context, themeId), - style: settings.scaleFonts( - defaultDropDownMenuItemTextStyle)), //"ddi_$themeId", key: Key("ddi_$themeId")), - ); - }).toList())), - leading: Icon(Icons.palette, - 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>( - (DualpaneMode value) { - return DropdownMenuItem( - 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>( - (DualpaneMode value) { - return DropdownMenuItem( - 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.defaultButtonActiveColor, - inactiveTrackColor: - settings.theme.defaultButtonDisabledColor, - secondary: Icon(Icons.text_fields, - color: settings.current().mainTextColor), - ), - ])))), + GlobalSettingsBehaviourView(), - // **** Behaviour - Scrollbar( - key: Key("BehaviourSettingsView"), - trackVisibility: true, - controller: settingsListScrollController, - child: SingleChildScrollView( - clipBehavior: Clip.antiAlias, - controller: settingsListScrollController, - padding: EdgeInsets.symmetric(vertical: 0, horizontal: 20), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: viewportConstraints.maxHeight, - ), - child: Column(children: [ - Visibility( - visible: Platform.isAndroid, - child: SwitchListTile( - title: Text(AppLocalizations.of(context)! - .settingAndroidPowerExemption), - subtitle: Text(AppLocalizations.of(context)! - .settingAndroidPowerExemptionDescription), - value: powerExempt, - onChanged: (bool value) { - if (value) { - requestBatteryExemption(); - } else { - // We can't ask for it to be turned off, show an informational popup - showBatteryDialog(context); - } - }, - activeTrackColor: settings.theme.defaultButtonColor, - inactiveTrackColor: - settings.theme.defaultButtonDisabledColor, - secondary: Icon(Icons.power, - color: settings.current().mainTextColor), - ), - ), - ListTile( - title: Text(AppLocalizations.of(context)! - .notificationPolicySettingLabel), - subtitle: Text(AppLocalizations.of(context)! - .notificationPolicySettingDescription), - trailing: Container( - width: MediaQuery.of(context).size.width / 4, - child: DropdownButton( - isExpanded: true, - value: settings.notificationPolicy, - onChanged: (NotificationPolicy? newValue) { - settings.notificationPolicy = newValue!; - saveSettings(context); - }, - items: NotificationPolicy.values.map< - DropdownMenuItem>( - (NotificationPolicy value) { - return DropdownMenuItem( - value: value, - child: Text( - Settings.notificationPolicyToString( - value, context), - overflow: TextOverflow.ellipsis, - style: settings.scaleFonts( - defaultDropDownMenuItemTextStyle)), - ); - }).toList())), - leading: Icon(CwtchIcons.chat_bubble_empty_24px, - color: settings.current().mainTextColor), - ), - ListTile( - title: Text(AppLocalizations.of(context)! - .notificationContentSettingLabel), - subtitle: Text(AppLocalizations.of(context)! - .notificationContentSettingDescription), - trailing: Container( - width: MediaQuery.of(context).size.width / 4, - child: DropdownButton( - isExpanded: true, - value: settings.notificationContent, - onChanged: (NotificationContent? newValue) { - settings.notificationContent = newValue!; - saveSettings(context); - }, - items: NotificationContent.values.map< - DropdownMenuItem< - NotificationContent>>( - (NotificationContent value) { - return DropdownMenuItem< - NotificationContent>( - value: value, - child: Text( - Settings.notificationContentToString( - value, context), - overflow: TextOverflow.ellipsis, - style: settings.scaleFonts( - defaultDropDownMenuItemTextStyle)), - ); - }).toList())), - leading: Icon(CwtchIcons.chat_bubble_empty_24px, - color: settings.current().mainTextColor), - ), - SwitchListTile( - title: Text( - AppLocalizations.of(context)!.blockUnknownLabel), - subtitle: Text(AppLocalizations.of(context)! - .descriptionBlockUnknownConnections), - value: settings.blockUnknownConnections, - onChanged: (bool value) { - if (value) { - settings.forbidUnknownConnections(); - } else { - settings.allowUnknownConnections(); - } + GlobalSettingsExperimentsView(), - // Save Settings... - saveSettings(context); - }, - activeTrackColor: settings.theme.defaultButtonColor, - inactiveTrackColor: - settings.theme.defaultButtonDisabledColor, - secondary: Icon(CwtchIcons.block_unknown, - color: settings.current().mainTextColor), - ), - SwitchListTile( - title: Text(AppLocalizations.of(context)! - .defaultPreserveHistorySetting), - subtitle: Text(AppLocalizations.of(context)! - .preserveHistorySettingDescription), - value: settings.preserveHistoryByDefault, - onChanged: (bool value) { - if (value) { - settings.setPreserveHistoryDefault(); - } else { - settings.setDeleteHistoryDefault(); - } - - // Save Settings... - saveSettings(context); - }, - activeTrackColor: settings.theme.defaultButtonColor, - inactiveTrackColor: - settings.theme.defaultButtonDisabledColor, - secondary: Icon(CwtchIcons.peer_history, - color: settings.current().mainTextColor), - ), - ])))), - - // ****** Experiments - Scrollbar( - key: Key("ExperimentsSettingsView"), - trackVisibility: true, - controller: settingsListScrollController, - child: SingleChildScrollView( - clipBehavior: Clip.antiAlias, - controller: settingsListScrollController, - padding: EdgeInsets.symmetric(vertical: 0, horizontal: 20), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: viewportConstraints.maxHeight, - ), - child: Column(children: [ - SwitchListTile( - title: Text( - AppLocalizations.of(context)!.experimentsEnabled), - subtitle: Text(AppLocalizations.of(context)! - .descriptionExperiments), - value: settings.experimentsEnabled, - onChanged: (bool value) { - if (value) { - settings.enableExperiments(); - } else { - settings.disableExperiments(); - } - // Save Settings... - saveSettings(context); - }, - activeTrackColor: settings.theme.defaultButtonColor, - inactiveTrackColor: - settings.theme.defaultButtonDisabledColor, - secondary: Icon(CwtchIcons.enable_experiments, - color: settings.current().mainTextColor), - ), - Visibility( - visible: settings.experimentsEnabled, - child: Column( - children: [ - SwitchListTile( - title: Text(AppLocalizations.of(context)! - .enableGroups), - subtitle: Text(AppLocalizations.of(context)! - .descriptionExperimentsGroups), - value: settings.isExperimentEnabled( - TapirGroupsExperiment), - onChanged: (bool value) { - if (value) { - settings.enableExperiment( - TapirGroupsExperiment); - } else { - settings.disableExperiment( - TapirGroupsExperiment); - } - // Save Settings... - saveSettings(context); - }, - activeTrackColor: - settings.theme.defaultButtonColor, - inactiveTrackColor: - settings.theme.defaultButtonDisabledColor, - secondary: Icon(CwtchIcons.enable_groups, - color: settings.current().mainTextColor), - ), - Visibility( - visible: - !Platform.isAndroid && !Platform.isIOS, - child: SwitchListTile( - title: Text(AppLocalizations.of(context)! - .settingServers), - subtitle: Provider.of( - context, - listen: false) - .cwtch - .IsServersCompiled() - ? Text(AppLocalizations.of(context)! - .settingServersDescription) - : Text( - "This version of Cwtch has been compiled without support for the server hosting experiment."), - value: Provider.of(context, - listen: false) - .cwtch - .IsServersCompiled() && - settings.isExperimentEnabled( - ServerManagementExperiment), - onChanged: Provider.of( - context, - listen: false) - .cwtch - .IsServersCompiled() - ? (bool value) { - Provider.of( - context, - listen: false) - .clear(); - if (value) { - settings.enableExperiment( - ServerManagementExperiment); - } else { - settings.disableExperiment( - ServerManagementExperiment); - } - // Save Settings... - saveSettings(context); - } - : null, - activeTrackColor: - settings.theme.defaultButtonColor, - inactiveTrackColor: settings - .theme.defaultButtonDisabledColor, - inactiveThumbColor: settings - .theme.defaultButtonDisabledColor, - secondary: Icon(CwtchIcons.dns_24px), - )), - SwitchListTile( - title: Text(AppLocalizations.of(context)! - .settingFileSharing), - subtitle: Text(AppLocalizations.of(context)! - .descriptionFileSharing), - value: settings.isExperimentEnabled( - FileSharingExperiment), - onChanged: (bool value) { - if (value) { - if (checkDownloadDirectory( - context, settings)) { - settings.enableExperiment( - FileSharingExperiment); - } else { - settings.enableExperiment( - FileSharingExperiment); - settings.disableExperiment( - ImagePreviewsExperiment); - } - } else { - settings.disableExperiment( - FileSharingExperiment); - settings.disableExperiment( - ImagePreviewsExperiment); - } - saveSettings(context); - }, - activeTrackColor: - settings.theme.defaultButtonColor, - inactiveTrackColor: - settings.theme.defaultButtonDisabledColor, - secondary: Icon(CwtchIcons.attached_file_3, - color: settings.current().mainTextColor), - ), - Visibility( - visible: settings.isExperimentEnabled( - FileSharingExperiment), - child: Column(children: [ - SwitchListTile( - title: Text(AppLocalizations.of(context)! - .settingImagePreviews), - subtitle: Text( - AppLocalizations.of(context)! - .settingImagePreviewsDescription), - value: settings.isExperimentEnabled( - ImagePreviewsExperiment), - onChanged: (bool value) { - if (value) { - if (checkDownloadDirectory( - context, settings)) { - settings.enableExperiment( - ImagePreviewsExperiment); - } else { - settings.disableExperiment( - ImagePreviewsExperiment); - } - } else { - settings.disableExperiment( - ImagePreviewsExperiment); - } - saveSettings(context); - }, - activeTrackColor: settings - .theme.defaultButtonActiveColor, - inactiveTrackColor: settings - .theme.defaultButtonDisabledColor, - secondary: Icon(Icons.photo, - color: - settings.current().mainTextColor), - ), - Visibility( - visible: settings.isExperimentEnabled( - ImagePreviewsExperiment) && - !Platform.isAndroid, - child: CwtchFolderPicker( - testKey: Key("DownloadFolderPicker"), - label: AppLocalizations.of(context)! - .settingDownloadFolder, - initialValue: settings.downloadPath, - textStyle: settings.scaleFonts( - defaultDropDownMenuItemTextStyle), - description: AppLocalizations.of( - context)! - .fileSharingSettingsDownloadFolderDescription, - tooltip: AppLocalizations.of(context)! - .fileSharingSettingsDownloadFolderTooltip, - onSave: (newVal) { - settings.downloadPath = newVal; - saveSettings(context); - }, - ), - ), - ]), - ), - SwitchListTile( - title: Text(AppLocalizations.of(context)! - .blodeuweddExperimentEnable), - subtitle: Provider.of(context, - listen: false) - .cwtch - .IsBlodeuweddSupported() - ? Text(AppLocalizations.of(context)! - .blodeuweddDescription) - : Text(AppLocalizations.of(context)! - .blodeuweddNotSupported), - value: Provider.of(context, - listen: false) - .cwtch - .IsBlodeuweddSupported() && - settings.isExperimentEnabled( - BlodeuweddExperiment), - onChanged: Provider.of(context, - listen: false) - .cwtch - .IsBlodeuweddSupported() - ? (bool value) { - if (value) { - settings.enableExperiment( - BlodeuweddExperiment); - } else { - settings.disableExperiment( - BlodeuweddExperiment); - } - saveSettings(context); - } - : null, - activeTrackColor: - settings.theme.defaultButtonColor, - inactiveTrackColor: - settings.theme.defaultButtonDisabledColor, - inactiveThumbColor: - settings.theme.defaultButtonDisabledColor, - secondary: Icon(Icons.assistant, - color: settings.current().mainTextColor), - ), - Visibility( - visible: Provider.of(context, - listen: false) - .cwtch - .IsBlodeuweddSupported() && - settings.isExperimentEnabled( - BlodeuweddExperiment), - child: CwtchFolderPicker( - testKey: Key("DownloadFolderPicker"), - label: AppLocalizations.of(context)! - .settingDownloadFolder, - initialValue: settings.blodeuweddPath, - description: AppLocalizations.of(context)! - .blodeuweddPath, - tooltip: AppLocalizations.of(context)! - .blodeuweddPath, - onSave: (newVal) { - settings.blodeuweddPath = newVal; - saveSettings(context); - }, - ), - ), - ], - )), - Visibility( - visible: settings.experimentsEnabled, - child: SwitchListTile( - title: Text(AppLocalizations.of(context)! - .enableExperimentClickableLinks), - subtitle: Text(AppLocalizations.of(context)! - .experimentClickableLinksDescription), - value: settings.isExperimentEnabled( - ClickableLinksExperiment), - onChanged: (bool value) { - if (value) { - settings.enableExperiment( - ClickableLinksExperiment); - } else { - settings.disableExperiment( - ClickableLinksExperiment); - } - saveSettings(context); - }, - activeTrackColor: - settings.theme.defaultButtonActiveColor, - inactiveTrackColor: - settings.theme.defaultButtonDisabledColor, - secondary: Icon(Icons.link, - color: settings.current().mainTextColor), - )), - Visibility( - visible: settings.experimentsEnabled, - child: SwitchListTile( - title: Text(AppLocalizations.of(context)! - .enableExperimentQRCode), - subtitle: Text(AppLocalizations.of(context)! - .experimentQRCodeDescription), - value: settings - .isExperimentEnabled(QRCodeExperiment), - onChanged: (bool value) { - if (value) { - settings.enableExperiment(QRCodeExperiment); - } else { - settings.disableExperiment(QRCodeExperiment); - } - saveSettings(context); - }, - activeTrackColor: - settings.theme.defaultButtonActiveColor, - inactiveTrackColor: - settings.theme.defaultButtonDisabledColor, - secondary: Icon(Icons.qr_code, - color: settings.current().mainTextColor), - )), - ])))), - - // **** About - Scrollbar( - key: Key("AboutSettingsView"), - trackVisibility: true, - controller: settingsListScrollController, - child: SingleChildScrollView( - clipBehavior: Clip.antiAlias, - controller: settingsListScrollController, - padding: EdgeInsets.symmetric(vertical: 0, horizontal: 20), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: viewportConstraints.maxHeight, - ), - child: Column(children: [ - AboutListTile( - icon: appIcon, - applicationIcon: Padding( - padding: EdgeInsets.all(5), - child: Icon(CwtchIcons.cwtch_knott)), - applicationName: "Cwtch UI", - applicationLegalese: - '\u{a9} 2021-2023 Open Privacy Research Society', - aboutBoxChildren: [ - Padding( - padding: EdgeInsets.fromLTRB( - 24.0 + 10.0 + (appIcon.size ?? 24.0), - 16.0, - 0.0, - 0.0), - // About has 24 padding (ln 389) and there appears to be another 10 of padding in the widget - child: SelectableText( - AppLocalizations.of(context)! - .versionBuilddate - .replaceAll( - "%1", EnvironmentConfig.BUILD_VER) - .replaceAll("%2", - EnvironmentConfig.BUILD_DATE)), - ) - ]), - SwitchListTile( - // TODO: Translate, Remove, OR Hide Prior to Release - title: Text("Show Performance Overlay"), - subtitle: - Text("Display an overlay graph of render time."), - value: settings.profileMode, - onChanged: (bool value) { - setState(() { - if (value) { - settings.profileMode = value; - } else { - settings.profileMode = value; - } - }); - }, - activeTrackColor: - settings.theme.defaultButtonActiveColor, - inactiveTrackColor: - settings.theme.defaultButtonDisabledColor, - secondary: Icon(Icons.bar_chart, - color: settings.current().mainTextColor), - ), - Visibility( - visible: - EnvironmentConfig.BUILD_VER == dev_version && - !Platform.isAndroid, - child: SwitchListTile( - title: Text("Show Semantic Debugger"), - subtitle: - Text("Show Accessibility Debugging View"), - value: settings.useSemanticDebugger, - onChanged: (bool value) { - if (value) { - settings.useSemanticDebugger = value; - } else { - settings.useSemanticDebugger = value; - } - saveSettings(context); - }, - activeTrackColor: - settings.theme.defaultButtonActiveColor, - inactiveTrackColor: - settings.theme.defaultButtonDisabledColor, - secondary: Icon(Icons.settings_accessibility, - color: settings.current().mainTextColor), - )), - Visibility( - visible: EnvironmentConfig.BUILD_VER == dev_version && - !Platform.isAndroid, - child: FutureBuilder( - future: - EnvironmentConfig.BUILD_VER != dev_version || - Platform.isAndroid - ? null - : Provider.of(context) - .cwtch - .GetDebugInfo(), - builder: (context, snapshot) { - if (snapshot.hasData) { - return Column( - children: [ - Text("libCwtch Debug Info: " + - snapshot.data.toString()), - Text("Message Cache Size (Mb): " + - (Provider.of(context) - .profs - .cacheMemUsage() / - (1024 * 1024)) - .toString()) - ], - ); - } else { - return Container(); - } - }, - ), - ), - Visibility( - visible: EnvironmentConfig.BUILD_VER == dev_version, - child: FutureBuilder( - future: Provider.of(context) - .cwtch - .PlatformChannelInfo(), - builder: (context, snapshot) { - if (snapshot.hasData) { - HashMap data = snapshot.data - as HashMap; - return getPlatformInfo(settings, data); - } - return Container(); - })) - ])))) + GlobalSettingsAboutView(), ]); }); }); } - - showBatteryDialog(BuildContext context) { - Widget okButton = ElevatedButton( - child: Text(AppLocalizations.of(context)!.okButton), - onPressed: () { - Navigator.of(context).pop(); - }, - ); - - // set up the AlertDialog - AlertDialog alert = AlertDialog( - title: - Text(AppLocalizations.of(context)!.settingsAndroidPowerReenablePopup), - actions: [ - okButton, - ], - ); - - // show the dialog - showDialog( - context: context, - builder: (BuildContext context) { - return alert; - }, - ); - } - - getPlatformInfo(settings, HashMap platformChannelInfo) { - var sortedKeys = platformChannelInfo.keys.toList(); - sortedKeys.sort(); - var widgets = List.empty(growable: true); - sortedKeys.forEach((element) { - widgets.add(ListTile( - leading: Icon(Icons.android, color: settings.current().mainTextColor), - title: Text(element), - subtitle: Text(platformChannelInfo[element]!), - )); - }); - return Column( - children: widgets, - ); - } -} - -bool checkDownloadDirectory(context, settings) { - bool showError = false; - if (settings.downloadPath != "") { - } else { - // check if the default download path exists - var path = Provider.of(context, listen: false) - .cwtch - .defaultDownloadPath(); - if (path != null) { - settings.downloadPath = path; - } else { - showError = true; - } - } - - if (!showError && Directory(settings.downloadPath).existsSync()) { - return true; - } else { - final snackBar = SnackBar( - content: Text( - AppLocalizations.of(context)!.errorDownloadDirectoryDoesNotExist), - ); - ScaffoldMessenger.of(context).showSnackBar(snackBar); - return false; - } -} - -/// Construct a version string from Package Info -String constructVersionString(PackageInfo pinfo) { - if (pinfo == null) { - return ""; - } - return pinfo.version + "." + pinfo.buildNumber; -} - -/// 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, 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 theme; } /// Send an UpdateGlobalSettings to the Event Bus