// SPDX-FileCopyrightText: 2019-Present Christian Kußowski // SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat // // SPDX-License-Identifier: AGPL-3.0-or-later import 'dart:convert'; import 'package:async/async.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:managed_configurations/managed_configurations.dart'; import 'package:matrix/matrix_api_lite/utils/logs.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:tatchat/utils/platform_infos.dart'; enum AppSettings { textMessageMaxLength('textMessageMaxLength', 16384), /// Max lines for unselected HTML/text bubbles; 0 = unlimited (no fade). messagePreviewMaxLines('chat.fluffy.message_preview_max_lines', 50), audioRecordingNumChannels('audioRecordingNumChannels', 1), audioRecordingAutoGain('audioRecordingAutoGain', true), audioRecordingEchoCancel('audioRecordingEchoCancel', false), audioRecordingNoiseSuppress('audioRecordingNoiseSuppress', true), audioRecordingBitRate('audioRecordingBitRate', 64000), audioRecordingSamplingRate('audioRecordingSamplingRate', 44100), showNoGoogle('chat.fluffy.show_no_google', false), unifiedPushRegistered('chat.fluffy.unifiedpush.registered', false), unifiedPushEndpoint('chat.fluffy.unifiedpush.endpoint', ''), pushNotificationsGatewayUrl( 'pushNotificationsGatewayUrl', 'https://push.tatuchat.weltonmoura.com.br/_matrix/push/v1/notify', ), pushNotificationsPusherFormat( 'pushNotificationsPusherFormat', 'event_id_only', ), renderHtml('chat.fluffy.renderHtml', true), fontSizeFactor('chat.fluffy.font_size_factor', 1.0), hideRedactedEvents('chat.fluffy.hideRedactedEvents', false), hideUnknownEvents('chat.fluffy.hideUnknownEvents', true), autoplayImages('chat.fluffy.autoplay_images', true), sendTypingNotifications('chat.fluffy.send_typing_notifications', true), sendPublicReadReceipts('chat.fluffy.send_public_read_receipts', true), swipeRightToLeftToReply('chat.fluffy.swipeRightToLeftToReply', true), sendOnEnter('chat.fluffy.send_on_enter', false), displayNavigationRail('chat.fluffy.display_navigation_rail', false), shareKeysWith('chat.fluffy.share_keys_with_2', 'all'), noEncryptionWarningShown( 'chat.fluffy.no_encryption_warning_shown', false, ), displayChatDetailsColumn('chat.fluffy.display_chat_details_column', false), // AppConfig-mirrored settings applicationName('chat.fluffy.application_name', 'TatuChat'), defaultHomeserver( 'chat.fluffy.default_homeserver', 'https://matrix.weltonmoura.com.br', ), // colorSchemeSeed stored as ARGB int colorSchemeSeedInt('chat.fluffy.color_scheme_seed', 0xFF00D084), emojiSuggestionLocale('emoji_suggestion_locale', ''), enableSoftLogout('chat.fluffy.enable_soft_logout', true), enableMatrixNativeOIDC('chat.fluffy.enable_matrix_native_oidc', true), presetHomeserver( 'chat.fluffy.preset_homeserver', 'https://matrix.weltonmoura.com.br', ), welcomeText('chat.fluffy.welcome_text', ''), website('chat.fluffy.website_url', 'https://tatuchat.weltonmoura.com.br'), logoUrl( 'chat.fluffy.logo_url', 'https://tatuchat.weltonmoura.com.br/assets/assets/logo/mini/logo_mini.png', ), privacyPolicy( 'chat.fluffy.privacy_policy_url', 'http://tatuchat.weltonmoura.com.br/politica-de-privacidade/', ), tos('chat.fluffy.tos_url', 'https://tatuchat.weltonmoura.com.br/tos'), sendTimelineEventTimeout('chat.fluffy.send_timeline_event_timeout', 15), webNotificationSound('chat.fluffy.web_notification_sound', true), chatFilter('chat.fluffy.chat_filter', 'allChats'), hideRoomsInSpaces('chat.fluffy.hideRoomsInSpaces', false), showThumbnailsInTimeline('chat.fluffy.showThumbnailsInTimeline', true), doubleTapToReact('chat.fluffy.double_tap_to_react', false), doubleTapReaction('chat.fluffy.double_tap_reaction', '❤️'), benchmarksInLogs('chat.fluffy.benchmarks_in_logs', false), autoSendErrorReports('chat.fluffy.auto_send_eror_reports', null), knownErrorHashes>('chat.fluffy.known_crash_hashes', []), customLiveKitInstance('chat.fluffy.custom_live_kit_instance', ''), /// URL of a CORS proxy used as a fallback when importing a mini app from a /// URL on web, if the target server does not send CORS headers. Should end /// with `?url=` or `?` (the target URL gets appended). Empty means disabled. miniAppCorsProxy('chat.fluffy.miniapp_cors_proxy', ''); final String key; final T _defaultValue; const AppSettings(this.key, this._defaultValue); static SharedPreferences get store => _store!; static SharedPreferences? _store; static Map? _platformConfiguration; T get defaultValue { final platformConfig = _platformConfiguration?[name]; if (platformConfig is T) return platformConfig; return _defaultValue; } static Future reset({bool loadWebConfigFile = true}) async { await AppSettings._store!.clear(); await init(loadWebConfigFile: loadWebConfigFile); } static Future init({bool loadWebConfigFile = true}) async { if (AppSettings._store != null) return AppSettings.store; final store = AppSettings._store = await SharedPreferences.getInstance(); // Migrate wrong datatype for fontSizeFactor final fontSizeFactorString = Result( () => store.getString(AppSettings.fontSizeFactor.key), ).asValue?.value; if (fontSizeFactorString != null) { Logs().i('Migrate wrong datatype for fontSizeFactor!'); await store.remove(AppSettings.fontSizeFactor.key); final fontSizeFactor = double.tryParse(fontSizeFactorString); if (fontSizeFactor != null) { await store.setDouble(AppSettings.fontSizeFactor.key, fontSizeFactor); } } if (store.getBool(AppSettings.sendOnEnter.key) == null) { await store.setBool(AppSettings.sendOnEnter.key, !PlatformInfos.isMobile); } if (store.getBool(AppSettings.doubleTapToReact.key) == null) { await store.setBool( AppSettings.doubleTapToReact.key, PlatformInfos.isMobile, ); } // Load configuration from config.json file or MDM: if (PlatformInfos.isMobile) { _platformConfiguration = await ManagedConfigurations().getManagedConfigurations; } else if (kIsWeb && loadWebConfigFile) { try { final configJsonString = utf8.decode( (await http.get(Uri.parse('config.json'))).bodyBytes, ); _platformConfiguration = json.decode(configJsonString) as Map; } on FormatException catch (_) { Logs().v('[ConfigLoader] config.json not found'); } catch (e) { Logs().v('[ConfigLoader] config.json not found', e); } } return store; } } extension AppSettingsBoolNExtension on AppSettings { bool? get value { final value = Result(() => AppSettings.store.getBool(key)); final error = value.asError; if (error != null) { Logs().e( 'Unable to fetch $key from storage. Removing entry...', error.error, error.stackTrace, ); } return value.asValue?.value; } Future setItem(bool value) => AppSettings.store.setBool(key, value); } extension AppSettingsBoolExtension on AppSettings { bool get value { final value = Result(() => AppSettings.store.getBool(key)); final error = value.asError; if (error != null) { Logs().e( 'Unable to fetch $key from storage. Removing entry...', error.error, error.stackTrace, ); } return value.asValue?.value ?? defaultValue; } Future setItem(bool value) => AppSettings.store.setBool(key, value); } extension AppSettingsStringExtension on AppSettings { String get value { final value = Result(() => AppSettings.store.getString(key)); final error = value.asError; if (error != null) { Logs().e( 'Unable to fetch $key from storage. Removing entry...', error.error, error.stackTrace, ); } return value.asValue?.value ?? defaultValue; } Future setItem(String value) => AppSettings.store.setString(key, value); } extension AppSettingsIntExtension on AppSettings { int get value { final value = Result(() => AppSettings.store.getInt(key)); final error = value.asError; if (error != null) { Logs().e( 'Unable to fetch $key from storage. Removing entry...', error.error, error.stackTrace, ); } return value.asValue?.value ?? defaultValue; } Future setItem(int value) => AppSettings.store.setInt(key, value); } extension AppSettingsDoubleExtension on AppSettings { double get value { final value = Result(() => AppSettings.store.getDouble(key)); final error = value.asError; if (error != null) { Logs().e( 'Unable to fetch $key from storage. Removing entry...', error.error, error.stackTrace, ); } return value.asValue?.value ?? defaultValue; } Future setItem(double value) => AppSettings.store.setDouble(key, value); } extension AppSettingsStringListExtension on AppSettings> { List get value { final value = Result(() => AppSettings.store.getStringList(key)); final error = value.asError; if (error != null) { Logs().e( 'Unable to fetch $key from storage. Removing entry...', error.error, error.stackTrace, ); } return value.asValue?.value ?? defaultValue; } Future setItem(List value) => AppSettings.store.setStringList(key, value); }