LCOV - code coverage report
Current view: top level - lib - settings.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 7 226 3.1 %
Date: 2024-04-05 23:55:39 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:collection';
       2             : import 'dart:core';
       3             : 
       4             : import 'package:flutter/material.dart';
       5             : import 'package:package_info_plus/package_info_plus.dart';
       6             : 
       7             : import 'themes/opaque.dart';
       8             : import 'package:flutter_gen/gen_l10n/app_localizations.dart';
       9             : 
      10             : const TapirGroupsExperiment = "tapir-groups-experiment";
      11             : const ServerManagementExperiment = "servers-experiment";
      12             : const FileSharingExperiment = "filesharing";
      13             : const ImagePreviewsExperiment = "filesharing-images";
      14             : const ClickableLinksExperiment = "clickable-links";
      15             : const FormattingExperiment = "message-formatting";
      16             : const QRCodeExperiment = "qrcode-support";
      17             : const BlodeuweddExperiment = "blodeuwedd";
      18             : 
      19             : enum DualpaneMode {
      20             :   Single,
      21             :   Dual1to2,
      22             :   Dual1to4,
      23             :   CopyPortrait,
      24             : }
      25             : 
      26             : enum NotificationPolicy {
      27             :   Mute,
      28             :   OptIn,
      29             :   DefaultAll,
      30             : }
      31             : 
      32             : enum NotificationContent {
      33             :   SimpleEvent,
      34             :   ContactInfo,
      35             : }
      36             : 
      37             : /// Settings govern the *Globally* relevant settings like Locale, Theme and Experiments.
      38             : /// We also provide access to the version information here as it is also accessed from the
      39             : /// Settings Pane.
      40             : class Settings extends ChangeNotifier {
      41             :   Locale locale;
      42             :   late PackageInfo packageInfo;
      43             :   bool _themeImages = false;
      44             : 
      45             :   // explicitly set experiments to false until told otherwise...
      46             :   bool experimentsEnabled = false;
      47             :   HashMap<String, bool> experiments = HashMap.identity();
      48             :   DualpaneMode _uiColumnModePortrait = DualpaneMode.Single;
      49             :   DualpaneMode _uiColumnModeLandscape = DualpaneMode.CopyPortrait;
      50             : 
      51             :   NotificationPolicy _notificationPolicy = NotificationPolicy.DefaultAll;
      52             :   NotificationContent _notificationContent = NotificationContent.SimpleEvent;
      53             : 
      54             :   bool preserveHistoryByDefault = false;
      55             :   bool blockUnknownConnections = false;
      56             :   bool streamerMode = false;
      57             :   String _downloadPath = "";
      58             : 
      59             :   bool _allowAdvancedTorConfig = false;
      60             :   bool _useCustomTorConfig = false;
      61             :   String _customTorConfig = "";
      62             :   int _socksPort = -1;
      63             :   int _controlPort = -1;
      64             :   String _customTorAuth = "";
      65             :   bool _useTorCache = false;
      66             :   String _torCacheDir = "";
      67             :   bool _useSemanticDebugger = false;
      68             :   double _fontScaling = 1.0;
      69             : 
      70             :   ThemeLoader themeloader = ThemeLoader();
      71             : 
      72           0 :   String get torCacheDir => _torCacheDir;
      73             : 
      74             :   // Whether to show the profiling interface, not saved
      75             :   bool _profileMode = false;
      76             : 
      77           0 :   bool get profileMode => _profileMode;
      78           0 :   set profileMode(bool newval) {
      79           0 :     this._profileMode = newval;
      80           0 :     notifyListeners();
      81             :   }
      82             : 
      83           0 :   set useSemanticDebugger(bool newval) {
      84           0 :     this._useSemanticDebugger = newval;
      85           0 :     notifyListeners();
      86             :   }
      87             : 
      88           0 :   bool get useSemanticDebugger => _useSemanticDebugger;
      89             : 
      90             :   String? _themeId;
      91           0 :   String? get themeId => _themeId;
      92             :   String? _mode;
      93          20 :   OpaqueThemeType get theme => themeloader.getTheme(_themeId, _mode);
      94           0 :   void setTheme(String themeId, String mode) {
      95           0 :     _themeId = themeId;
      96           0 :     _mode = mode;
      97           0 :     notifyListeners();
      98             :   }
      99             : 
     100           0 :   bool get themeImages => _themeImages;
     101           0 :   set themeImages(bool newVal) {
     102           0 :     _themeImages = newVal;
     103           0 :     notifyListeners();
     104             :   }
     105             : 
     106             :   /// Get access to the current theme.
     107           4 :   OpaqueThemeType current() {
     108           4 :     return theme;
     109             :   }
     110             : 
     111             :   /// isExperimentEnabled can be used to safely check whether a particular
     112             :   /// experiment is enabled
     113           0 :   bool isExperimentEnabled(String experiment) {
     114           0 :     if (this.experimentsEnabled) {
     115           0 :       if (this.experiments.containsKey(experiment)) {
     116             :         // We now know it cannot be null...
     117           0 :         return this.experiments[experiment]! == true;
     118             :       }
     119             :     }
     120             : 
     121             :     // allow message formatting to be turned off even when experiments are
     122             :     // disabled...
     123           0 :     if (experiment == FormattingExperiment) {
     124           0 :       if (this.experiments.containsKey(FormattingExperiment)) {
     125             :         // If message formatting has not explicitly been turned off, then
     126             :         // turn it on by default (even when experiments are disabled)
     127           0 :         return this.experiments[experiment]! == true;
     128             :       } else {
     129             :         return true; // enable by default
     130             :       }
     131             :     }
     132             : 
     133             :     return false;
     134             :   }
     135             : 
     136             :   /// Called by the event bus. When new settings are loaded from a file the JSON will
     137             :   /// be sent to the function and new settings will be instantiated based on the contents.
     138           0 :   handleUpdate(dynamic settings) {
     139             :     // Set Theme and notify listeners
     140           0 :     this.setTheme(settings["Theme"], settings["ThemeMode"] ?? mode_dark);
     141           0 :     _themeImages = settings["ThemeImages"] ?? false;
     142             : 
     143             :     // Set Locale and notify listeners
     144           0 :     switchLocaleByCode(settings["Locale"]);
     145             : 
     146             :     // Decide whether to enable Experiments
     147           0 :     var fontScale = settings["FontScaling"];
     148             :     if (fontScale == null) {
     149             :       fontScale = 1.0;
     150             :     }
     151           0 :     _fontScaling = double.parse(fontScale.toString()).clamp(0.5, 2.0);
     152             : 
     153           0 :     blockUnknownConnections = settings["BlockUnknownConnections"] ?? false;
     154           0 :     streamerMode = settings["StreamerMode"] ?? false;
     155             : 
     156             :     // Decide whether to enable Experiments
     157           0 :     experimentsEnabled = settings["ExperimentsEnabled"] ?? false;
     158           0 :     preserveHistoryByDefault = settings["DefaultSaveHistory"] ?? false;
     159             : 
     160             :     // Set the internal experiments map. Casting from the Map<dynamic, dynamic> that we get from JSON
     161           0 :     experiments = new HashMap<String, bool>.from(settings["Experiments"]);
     162             : 
     163             :     // single pane vs dual pane preferences
     164           0 :     _uiColumnModePortrait = uiColumnModeFromString(settings["UIColumnModePortrait"]);
     165           0 :     _uiColumnModeLandscape = uiColumnModeFromString(settings["UIColumnModeLandscape"]);
     166           0 :     _notificationPolicy = notificationPolicyFromString(settings["NotificationPolicy"]);
     167             : 
     168           0 :     _notificationContent = notificationContentFromString(settings["NotificationContent"]);
     169             : 
     170             :     // auto-download folder
     171           0 :     _downloadPath = settings["DownloadPath"] ?? "";
     172           0 :     _blodeuweddPath = settings["BlodeuweddPath"] ?? "";
     173             : 
     174             :     // allow a custom tor config
     175           0 :     _allowAdvancedTorConfig = settings["AllowAdvancedTorConfig"] ?? false;
     176           0 :     _useCustomTorConfig = settings["UseCustomTorrc"] ?? false;
     177           0 :     _customTorConfig = settings["CustomTorrc"] ?? "";
     178           0 :     _socksPort = settings["CustomSocksPort"] ?? -1;
     179           0 :     _controlPort = settings["CustomControlPort"] ?? -1;
     180           0 :     _useTorCache = settings["UseTorCache"] ?? false;
     181           0 :     _torCacheDir = settings["TorCacheDir"] ?? "";
     182             : 
     183             :     // Push the experimental settings to Consumers of Settings
     184           0 :     notifyListeners();
     185             :   }
     186             : 
     187             :   /// Initialize the Package Version information
     188           0 :   initPackageInfo() {
     189           0 :     PackageInfo.fromPlatform().then((PackageInfo newPackageInfo) {
     190           0 :       packageInfo = newPackageInfo;
     191           0 :       notifyListeners();
     192             :     });
     193             :   }
     194             : 
     195             :   /// Switch the Locale of the App by Language Code
     196           0 :   switchLocaleByCode(String languageCode) {
     197           0 :     var code = languageCode.split("_");
     198           0 :     if (code.length == 1) {
     199           0 :       this.switchLocale(Locale(languageCode));
     200             :     } else {
     201           0 :       this.switchLocale(Locale(code[0], code[1]));
     202             :     }
     203             :   }
     204             : 
     205             :   /// Handle Font Scaling
     206           0 :   set fontScaling(double newFontScaling) {
     207           0 :     this._fontScaling = newFontScaling;
     208           0 :     notifyListeners();
     209             :   }
     210             : 
     211           8 :   double get fontScaling => _fontScaling;
     212             : 
     213             :   // a convenience function to scale fonts dynamically...
     214           4 :   TextStyle scaleFonts(TextStyle input) {
     215          16 :     return input.copyWith(fontSize: (input.fontSize ?? 12) * this.fontScaling);
     216             :   }
     217             : 
     218             :   /// Switch the Locale of the App
     219           0 :   switchLocale(Locale newLocale) {
     220           0 :     locale = newLocale;
     221           0 :     notifyListeners();
     222             :   }
     223             : 
     224           0 :   setStreamerMode(bool newSteamerMode) {
     225           0 :     streamerMode = newSteamerMode;
     226           0 :     notifyListeners();
     227             :   }
     228             : 
     229             :   /// Preserve the History of all Conversations By Default (can be overridden for specific conversations)
     230           0 :   setPreserveHistoryDefault() {
     231           0 :     preserveHistoryByDefault = true;
     232           0 :     notifyListeners();
     233             :   }
     234             : 
     235             :   /// Delete the History of all Conversations By Default (can be overridden for specific conversations)
     236           0 :   setDeleteHistoryDefault() {
     237           0 :     preserveHistoryByDefault = false;
     238           0 :     notifyListeners();
     239             :   }
     240             : 
     241             :   /// Block Unknown Connections will autoblock connections if they authenticate with public key not in our contacts list.
     242             :   /// This is one of the best tools we have to combat abuse, while it isn't ideal it does allow a user to curate their contacts
     243             :   /// list without being bothered by spurious requests (either permanently, or as a short term measure).
     244             :   /// Note: This is not an *appear offline* setting which would explicitly close the listen port, rather than simply auto disconnecting unknown attempts.
     245           0 :   forbidUnknownConnections() {
     246           0 :     blockUnknownConnections = true;
     247           0 :     notifyListeners();
     248             :   }
     249             : 
     250             :   /// Allow Unknown Connections will allow new contact requires from unknown public keys
     251             :   /// See above for more information.
     252           0 :   allowUnknownConnections() {
     253           0 :     blockUnknownConnections = false;
     254           0 :     notifyListeners();
     255             :   }
     256             : 
     257             :   /// Turn Experiments On, this will also have the side effect of enabling any
     258             :   /// Experiments that have been previously activated.
     259           0 :   enableExperiments() {
     260           0 :     experimentsEnabled = true;
     261           0 :     notifyListeners();
     262             :   }
     263             : 
     264             :   /// Turn Experiments Off. This will disable **all** active experiments.
     265             :   /// Note: This will not set the preference for individual experiments, if experiments are enabled
     266             :   /// any experiments that were active previously will become active again unless they are explicitly disabled.
     267           0 :   disableExperiments() {
     268           0 :     experimentsEnabled = false;
     269           0 :     notifyListeners();
     270             :   }
     271             : 
     272             :   /// Turn on a specific experiment.
     273           0 :   enableExperiment(String key) {
     274           0 :     experiments.update(key, (value) => true, ifAbsent: () => true);
     275           0 :     notifyListeners();
     276             :   }
     277             : 
     278             :   /// Turn off a specific experiment
     279           0 :   disableExperiment(String key) {
     280           0 :     experiments.update(key, (value) => false, ifAbsent: () => false);
     281           0 :     notifyListeners();
     282             :   }
     283             : 
     284           0 :   DualpaneMode get uiColumnModePortrait => _uiColumnModePortrait;
     285             : 
     286           0 :   set uiColumnModePortrait(DualpaneMode newval) {
     287           0 :     this._uiColumnModePortrait = newval;
     288           0 :     notifyListeners();
     289             :   }
     290             : 
     291           0 :   DualpaneMode get uiColumnModeLandscape => _uiColumnModeLandscape;
     292             : 
     293           0 :   set uiColumnModeLandscape(DualpaneMode newval) {
     294           0 :     this._uiColumnModeLandscape = newval;
     295           0 :     notifyListeners();
     296             :   }
     297             : 
     298           0 :   NotificationPolicy get notificationPolicy => _notificationPolicy;
     299             : 
     300           0 :   set notificationPolicy(NotificationPolicy newpol) {
     301           0 :     this._notificationPolicy = newpol;
     302           0 :     notifyListeners();
     303             :   }
     304             : 
     305           0 :   NotificationContent get notificationContent => _notificationContent;
     306             : 
     307           0 :   set notificationContent(NotificationContent newcon) {
     308           0 :     this._notificationContent = newcon;
     309           0 :     notifyListeners();
     310             :   }
     311             : 
     312           0 :   List<int> uiColumns(bool isLandscape) {
     313           0 :     var m = (!isLandscape || uiColumnModeLandscape == DualpaneMode.CopyPortrait) ? uiColumnModePortrait : uiColumnModeLandscape;
     314             :     switch (m) {
     315           0 :       case DualpaneMode.Single:
     316           0 :         return [1];
     317           0 :       case DualpaneMode.Dual1to2:
     318           0 :         return [1, 2];
     319           0 :       case DualpaneMode.Dual1to4:
     320           0 :         return [1, 4];
     321             :     }
     322           0 :     print("impossible column configuration: portrait/$uiColumnModePortrait landscape/$uiColumnModeLandscape");
     323           0 :     return [1];
     324             :   }
     325             : 
     326           0 :   static List<DualpaneMode> uiColumnModeOptions(bool isLandscape) {
     327             :     if (isLandscape)
     328           0 :       return [
     329             :         DualpaneMode.CopyPortrait,
     330             :         DualpaneMode.Single,
     331             :         DualpaneMode.Dual1to2,
     332             :         DualpaneMode.Dual1to4,
     333             :       ];
     334             :     else
     335           0 :       return [DualpaneMode.Single, DualpaneMode.Dual1to2, DualpaneMode.Dual1to4];
     336             :   }
     337             : 
     338           0 :   static DualpaneMode uiColumnModeFromString(String m) {
     339             :     switch (m) {
     340           0 :       case "DualpaneMode.Single":
     341             :         return DualpaneMode.Single;
     342           0 :       case "DualpaneMode.Dual1to2":
     343             :         return DualpaneMode.Dual1to2;
     344           0 :       case "DualpaneMode.Dual1to4":
     345             :         return DualpaneMode.Dual1to4;
     346           0 :       case "DualpaneMode.CopyPortrait":
     347             :         return DualpaneMode.CopyPortrait;
     348             :     }
     349           0 :     print("Error: ui requested translation of column mode [$m] which doesn't exist");
     350             :     return DualpaneMode.Single;
     351             :   }
     352             : 
     353           0 :   static String uiColumnModeToString(DualpaneMode m, BuildContext context) {
     354             :     switch (m) {
     355           0 :       case DualpaneMode.Single:
     356           0 :         return AppLocalizations.of(context)!.settingUIColumnSingle;
     357           0 :       case DualpaneMode.Dual1to2:
     358           0 :         return AppLocalizations.of(context)!.settingUIColumnDouble12Ratio;
     359           0 :       case DualpaneMode.Dual1to4:
     360           0 :         return AppLocalizations.of(context)!.settingUIColumnDouble14Ratio;
     361           0 :       case DualpaneMode.CopyPortrait:
     362           0 :         return AppLocalizations.of(context)!.settingUIColumnOptionSame;
     363             :     }
     364             :   }
     365             : 
     366           0 :   static NotificationPolicy notificationPolicyFromString(String? np) {
     367             :     switch (np) {
     368           0 :       case "NotificationPolicy.Mute":
     369             :         return NotificationPolicy.Mute;
     370           0 :       case "NotificationPolicy.OptIn":
     371             :         return NotificationPolicy.OptIn;
     372           0 :       case "NotificationPolicy.OptOut":
     373             :         return NotificationPolicy.DefaultAll;
     374             :     }
     375             :     return NotificationPolicy.DefaultAll;
     376             :   }
     377             : 
     378           0 :   static NotificationContent notificationContentFromString(String? nc) {
     379             :     switch (nc) {
     380           0 :       case "NotificationContent.SimpleEvent":
     381             :         return NotificationContent.SimpleEvent;
     382           0 :       case "NotificationContent.ContactInfo":
     383             :         return NotificationContent.ContactInfo;
     384             :     }
     385             :     return NotificationContent.SimpleEvent;
     386             :   }
     387             : 
     388           0 :   static String notificationPolicyToString(NotificationPolicy np, BuildContext context) {
     389             :     switch (np) {
     390           0 :       case NotificationPolicy.Mute:
     391           0 :         return AppLocalizations.of(context)!.notificationPolicyMute;
     392           0 :       case NotificationPolicy.OptIn:
     393           0 :         return AppLocalizations.of(context)!.notificationPolicyOptIn;
     394           0 :       case NotificationPolicy.DefaultAll:
     395           0 :         return AppLocalizations.of(context)!.notificationPolicyDefaultAll;
     396             :     }
     397             :   }
     398             : 
     399           0 :   static String notificationContentToString(NotificationContent nc, BuildContext context) {
     400             :     switch (nc) {
     401           0 :       case NotificationContent.SimpleEvent:
     402           0 :         return AppLocalizations.of(context)!.notificationContentSimpleEvent;
     403           0 :       case NotificationContent.ContactInfo:
     404           0 :         return AppLocalizations.of(context)!.notificationContentContactInfo;
     405             :     }
     406             :   }
     407             : 
     408             :   // checks experiment settings and file extension for image previews
     409             :   // (ignores file size; if the user manually accepts the file, assume it's okay to preview)
     410           0 :   bool shouldPreview(String path) {
     411           0 :     return isExperimentEnabled(ImagePreviewsExperiment) && isImage(path);
     412             :   }
     413             : 
     414           0 :   bool isImage(String path) {
     415           0 :     var lpath = path.toLowerCase();
     416           0 :     return (lpath.endsWith(".jpg") || lpath.endsWith(".jpeg") || lpath.endsWith(".png") || lpath.endsWith(".gif") || lpath.endsWith(".webp") || lpath.endsWith(".bmp"));
     417             :   }
     418             : 
     419           0 :   String get downloadPath => _downloadPath;
     420             : 
     421           0 :   set downloadPath(String newval) {
     422           0 :     _downloadPath = newval;
     423           0 :     notifyListeners();
     424             :   }
     425             : 
     426           0 :   bool get allowAdvancedTorConfig => _allowAdvancedTorConfig;
     427             : 
     428           0 :   set allowAdvancedTorConfig(bool torConfig) {
     429           0 :     _allowAdvancedTorConfig = torConfig;
     430           0 :     notifyListeners();
     431             :   }
     432             : 
     433           0 :   bool get useTorCache => _useTorCache;
     434             : 
     435           0 :   set useTorCache(bool useTorCache) {
     436           0 :     _useTorCache = useTorCache;
     437           0 :     notifyListeners();
     438             :   }
     439             : 
     440             :   // Settings / Gettings for setting the custom tor config..
     441           0 :   String get torConfig => _customTorConfig;
     442             : 
     443           0 :   set torConfig(String torConfig) {
     444           0 :     _customTorConfig = torConfig;
     445           0 :     notifyListeners();
     446             :   }
     447             : 
     448           0 :   int get socksPort => _socksPort;
     449             : 
     450           0 :   set socksPort(int newSocksPort) {
     451           0 :     _socksPort = newSocksPort;
     452           0 :     notifyListeners();
     453             :   }
     454             : 
     455           0 :   int get controlPort => _controlPort;
     456             : 
     457           0 :   set controlPort(int controlPort) {
     458           0 :     _controlPort = controlPort;
     459           0 :     notifyListeners();
     460             :   }
     461             : 
     462             :   // Setters / Getters for toggling whether the app should use a custom tor config
     463           0 :   bool get useCustomTorConfig => _useCustomTorConfig;
     464             : 
     465           0 :   set useCustomTorConfig(bool useCustomTorConfig) {
     466           0 :     _useCustomTorConfig = useCustomTorConfig;
     467           0 :     notifyListeners();
     468             :   }
     469             : 
     470             :   /// Construct a default settings object.
     471           4 :   Settings(this.locale);
     472             : 
     473             :   String _blodeuweddPath = "";
     474           0 :   String get blodeuweddPath => _blodeuweddPath;
     475           0 :   set blodeuweddPath(String newval) {
     476           0 :     _blodeuweddPath = newval;
     477           0 :     notifyListeners();
     478             :   }
     479             : 
     480             :   /// Convert this Settings object to a JSON representation for serialization on the
     481             :   /// event bus.
     482           0 :   dynamic asJson() {
     483           0 :     return {
     484           0 :       "Locale": this.locale.toString(),
     485           0 :       "Theme": _themeId,
     486           0 :       "ThemeMode": theme.mode,
     487           0 :       "ThemeImages": _themeImages,
     488           0 :       "PreviousPid": -1,
     489           0 :       "BlockUnknownConnections": blockUnknownConnections,
     490           0 :       "NotificationPolicy": _notificationPolicy.toString(),
     491           0 :       "NotificationContent": _notificationContent.toString(),
     492           0 :       "StreamerMode": streamerMode,
     493           0 :       "ExperimentsEnabled": this.experimentsEnabled,
     494           0 :       "Experiments": experiments,
     495             :       "StateRootPane": 0,
     496             :       "FirstTime": false,
     497           0 :       "UIColumnModePortrait": uiColumnModePortrait.toString(),
     498           0 :       "UIColumnModeLandscape": uiColumnModeLandscape.toString(),
     499           0 :       "DownloadPath": _downloadPath,
     500           0 :       "AllowAdvancedTorConfig": _allowAdvancedTorConfig,
     501           0 :       "CustomTorRc": _customTorConfig,
     502           0 :       "UseCustomTorrc": _useCustomTorConfig,
     503           0 :       "CustomSocksPort": _socksPort,
     504           0 :       "CustomControlPort": _controlPort,
     505           0 :       "CustomAuth": _customTorAuth,
     506           0 :       "UseTorCache": _useTorCache,
     507           0 :       "TorCacheDir": _torCacheDir,
     508           0 :       "BlodeuweddPath": _blodeuweddPath,
     509           0 :       "FontScaling": _fontScaling,
     510           0 :       "DefaultSaveHistory": preserveHistoryByDefault
     511             :     };
     512             :   }
     513             : }

Generated by: LCOV version 1.14