refactor: Use AppSettings enum based configuration everywhere and fix load from json on web

pull/2264/head
Christian Kußowski 3 days ago
parent 4f0ed3e93f
commit 4c357f6249
No known key found for this signature in database
GPG Key ID: E067ECD60F1A0652

@ -48,9 +48,63 @@ Please visit the website for installation instructions:
# How to build
Please visit the [Wiki](https://github.com/krille-chan/fluffychat/wiki) for build instructions:
1. To build FluffyChat you need [Flutter](https://flutter.dev) and [Rust](https://www.rust-lang.org/tools/install)
- https://github.com/krille-chan/fluffychat/wiki/How-To-Build
2. Clone the repo:
```
git clone https://github.com/krille-chan/fluffychat.git
cd fluffychat
```
3. Choose your target platform below and enable support for it.
3.1 If you want, enable Googles Firebase Cloud Messaging:
`git apply ./scripts/enable-android-google-services.patch`
4. Debug with: `flutter run`
### Android
* Build with: `flutter build apk`
### iOS / iPadOS
* Have a Mac with Xcode installed, and set up for Xcode-managed app signing
* If you want automatic app installation to connected devices, make sure you have Apple Configurator installed, with the Automation Tools (`cfgutil`) enabled
* Set a few environment variables
* FLUFFYCHAT_NEW_TEAM: the Apple Developer team that your certificates should live under
* FLUFFYCHAT_NEW_GROUP: the group you want App IDs and such to live under (ie: com.example.fluffychat)
* FLUFFYCHAT_INSTALL_IPA: set to `1` if you want the IPA to be deployed to connected devices after building, otherwise unset
* Run `./scripts/build-ios.sh`
### Web
* Build with:
```bash
./scripts/prepare-web.sh # To install Vodozemac
flutter build web --release
```
* Optionally configure by serving a `config.json` at the same path as fluffychat.
An example can be found at `config.sample.json`. All values there are optional.
**Please only the values, you really need**. If you e.g. only want
to change the default homeserver, then only modify the `defaultHomeserver` key.
### Desktop (Linux, Windows, macOS)
* Enable Desktop support in Flutter: https://flutter.dev/desktop
#### Install custom dependencies (Linux)
```bash
sudo apt install libjsoncpp1 libsecret-1-dev libsecret-1-0 librhash0 libwebkit2gtk-4.0-dev
```
* Build with one of these:
```bash
flutter build linux --release
flutter build windows --release
flutter build macos --release
```
# Special thanks

@ -1,10 +1,29 @@
{
"application_name": "FluffyChat",
"application_welcome_message": null,
"default_homeserver": "matrix.org",
"web_base_url": "https://fluffychat.im/web",
"privacy_url": "https://fluffychat.im/en/privacy.html",
"render_html": false,
"hide_redacted_events": false,
"hide_unknown_events": false
"applicationName": "FluffyChat",
"defaultHomeserver": "matrix.org",
"privacyUrl": "https://github.com/krille-chan/fluffychat/blob/main/PRIVACY.md",
"audioRecordingNumChannels": 1,
"audioRecordingAutoGain": true,
"audioRecordingEchoCancel": false,
"audioRecordingNoiseSuppress": true,
"audioRecordingBitRate": 64000,
"audioRecordingSamplingRate": 44100,
"renderHtml": true,
"fontSizeFactor": 1,
"hideRedactedEvents": false,
"hideUnknownEvents": true,
"separateChatTypes": false,
"autoplayImages": true,
"sendTypingNotifications": true,
"sendPublicReadReceipts": true,
"swipeRightToLeftToReply": true,
"sendOnEnter": false,
"showPresences": true,
"displayNavigationRail": false,
"experimentalVoip": false,
"shareKeysWith": "all",
"noEncryptionWarningShown": false,
"displayChatDetailsColumn": false,
"colorSchemeSeedInt": 4283835834,
"enableSoftLogout": false
}

@ -1,4 +1,3 @@
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/pages/chat/chat_view.dart';
import 'package:fluffychat/pages/chat_list/chat_list_body.dart';
import 'package:fluffychat/pages/chat_list/search_title.dart';
@ -25,7 +24,7 @@ void main() {
() async {
// this random dialog popping up is super hard to cover in tests
SharedPreferences.setMockInitialValues({
SettingKeys.showNoGoogle: false,
'chat.fluffy.show_no_google': false,
});
},
);

@ -1,32 +1,25 @@
import 'dart:ui';
import 'package:matrix/matrix.dart';
abstract class AppConfig {
static String _applicationName = 'FluffyChat';
static String get applicationName => _applicationName;
static String? _applicationWelcomeMessage;
static String? get applicationWelcomeMessage => _applicationWelcomeMessage;
static String _defaultHomeserver = 'matrix.org';
// Const and final configuration values (immutable)
static const Color primaryColor = Color(0xFF5625BA);
static const Color primaryColorLight = Color(0xFFCCBDEA);
static const Color secondaryColor = Color(0xFF41a2bc);
static String get defaultHomeserver => _defaultHomeserver;
static double fontSizeFactor = 1;
static const Color chatColor = primaryColor;
static Color? colorSchemeSeed = primaryColor;
static const double messageFontSize = 16.0;
static const bool allowOtherHomeservers = true;
static const bool enableRegistration = true;
static const Color primaryColor = Color(0xFF5625BA);
static const Color primaryColorLight = Color(0xFFCCBDEA);
static const Color secondaryColor = Color(0xFF41a2bc);
static String _privacyUrl =
'https://github.com/krille-chan/fluffychat/blob/main/PRIVACY.md';
static const bool hideTypingUsernames = false;
static const Set<String> defaultReactions = {'👍', '❤️', '😂', '😮', '😢'};
static const String inviteLinkPrefix = 'https://matrix.to/#/';
static const String deepLinkPrefix = 'im.fluffychat://chat/';
static const String schemePrefix = 'matrix:';
static const String pushNotificationsChannelId = 'fluffychat_push';
static const String pushNotificationsAppId = 'chat.fluffy.fluffychat';
static const double borderRadius = 18.0;
static const double columnWidth = 360.0;
static String get privacyUrl => _privacyUrl;
static const String website = 'https://fluffychat.im';
static const String enablePushTutorial =
'https://github.com/krille-chan/fluffychat/wiki/Push-Notifications-without-Google-Services';
@ -36,80 +29,25 @@ abstract class AppConfig {
'https://github.com/krille-chan/fluffychat/wiki/How-to-Find-Users-in-FluffyChat';
static const String appId = 'im.fluffychat.FluffyChat';
static const String appOpenUrlScheme = 'im.fluffychat';
static String _webBaseUrl = 'https://fluffychat.im/web';
static String get webBaseUrl => _webBaseUrl;
static const String sourceCodeUrl =
'https://github.com/krille-chan/fluffychat';
static const String supportUrl =
'https://github.com/krille-chan/fluffychat/issues';
static const String changelogUrl =
'https://github.com/krille-chan/fluffychat/blob/main/CHANGELOG.md';
static const Set<String> defaultReactions = {'👍', '❤️', '😂', '😮', '😢'};
static final Uri newIssueUrl = Uri(
scheme: 'https',
host: 'github.com',
path: '/krille-chan/fluffychat/issues/new',
);
static bool renderHtml = true;
static bool hideRedactedEvents = false;
static bool hideUnknownEvents = true;
static bool separateChatTypes = false;
static bool autoplayImages = true;
static bool sendTypingNotifications = true;
static bool sendPublicReadReceipts = true;
static bool swipeRightToLeftToReply = true;
static bool? sendOnEnter;
static bool showPresences = true;
static bool displayNavigationRail = false;
static bool experimentalVoip = false;
static const bool hideTypingUsernames = false;
static const String inviteLinkPrefix = 'https://matrix.to/#/';
static const String deepLinkPrefix = 'im.fluffychat://chat/';
static const String schemePrefix = 'matrix:';
static const String pushNotificationsChannelId = 'fluffychat_push';
static const String pushNotificationsAppId = 'chat.fluffy.fluffychat';
static const double borderRadius = 18.0;
static const double columnWidth = 360.0;
static final Uri homeserverList = Uri(
scheme: 'https',
host: 'servers.joinmatrix.org',
path: 'servers.json',
);
static void loadFromJson(Map<String, dynamic> json) {
if (json['chat_color'] != null) {
try {
colorSchemeSeed = Color(json['chat_color']);
} catch (e) {
Logs().w(
'Invalid color in config.json! Please make sure to define the color in this format: "0xffdd0000"',
e,
);
}
}
if (json['application_name'] is String) {
_applicationName = json['application_name'];
}
if (json['application_welcome_message'] is String) {
_applicationWelcomeMessage = json['application_welcome_message'];
}
if (json['default_homeserver'] is String) {
_defaultHomeserver = json['default_homeserver'];
}
if (json['privacy_url'] is String) {
_privacyUrl = json['privacy_url'];
}
if (json['web_base_url'] is String) {
_webBaseUrl = json['web_base_url'];
}
if (json['render_html'] is bool) {
renderHtml = json['render_html'];
}
if (json['hide_redacted_events'] is bool) {
hideRedactedEvents = json['hide_redacted_events'];
}
if (json['hide_unknown_events'] is bool) {
hideUnknownEvents = json['hide_unknown_events'];
}
}
}

@ -1,40 +1,12 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:matrix/matrix_api_lite/utils/logs.dart';
import 'package:shared_preferences/shared_preferences.dart';
abstract class SettingKeys {
static const String renderHtml = 'chat.fluffy.renderHtml';
static const String hideRedactedEvents = 'chat.fluffy.hideRedactedEvents';
static const String hideUnknownEvents = 'chat.fluffy.hideUnknownEvents';
static const String hideUnimportantStateEvents =
'chat.fluffy.hideUnimportantStateEvents';
static const String separateChatTypes = 'chat.fluffy.separateChatTypes';
static const String sentry = 'sentry';
static const String theme = 'theme';
static const String amoledEnabled = 'amoled_enabled';
static const String codeLanguage = 'code_language';
static const String showNoGoogle = 'chat.fluffy.show_no_google';
static const String fontSizeFactor = 'chat.fluffy.font_size_factor';
static const String showNoPid = 'chat.fluffy.show_no_pid';
static const String databasePassword = 'database-password';
static const String appLockKey = 'chat.fluffy.app_lock';
static const String unifiedPushRegistered =
'chat.fluffy.unifiedpush.registered';
static const String unifiedPushEndpoint = 'chat.fluffy.unifiedpush.endpoint';
static const String ownStatusMessage = 'chat.fluffy.status_msg';
static const String dontAskForBootstrapKey =
'chat.fluffychat.dont_ask_bootstrap';
static const String autoplayImages = 'chat.fluffy.autoplay_images';
static const String sendTypingNotifications =
'chat.fluffy.send_typing_notifications';
static const String sendPublicReadReceipts =
'chat.fluffy.send_public_read_receipts';
static const String sendOnEnter = 'chat.fluffy.send_on_enter';
static const String swipeRightToLeftToReply =
'chat.fluffy.swipeRightToLeftToReply';
static const String experimentalVoip = 'chat.fluffy.experimental_voip';
static const String showPresences = 'chat.fluffy.show_presences';
static const String displayNavigationRail =
'chat.fluffy.display_navigation_rail';
}
import 'package:fluffychat/utils/platform_infos.dart';
enum AppSettings<T> {
textMessageMaxLength<int>('textMessageMaxLength', 16384),
@ -44,6 +16,9 @@ enum AppSettings<T> {
audioRecordingNoiseSuppress<bool>('audioRecordingNoiseSuppress', true),
audioRecordingBitRate<int>('audioRecordingBitRate', 64000),
audioRecordingSamplingRate<int>('audioRecordingSamplingRate', 44100),
showNoGoogle<bool>('chat.fluffy.show_no_google', false),
unifiedPushRegistered<bool>('chat.fluffy.unifiedpush.registered', false),
unifiedPushEndpoint<String>('chat.fluffy.unifiedpush.endpoint', ''),
pushNotificationsGatewayUrl<String>(
'pushNotificationsGatewayUrl',
'https://push.fluffychat.im/_matrix/push/v1/notify',
@ -52,6 +27,19 @@ enum AppSettings<T> {
'pushNotificationsPusherFormat',
'event_id_only',
),
renderHtml<bool>('chat.fluffy.renderHtml', true),
fontSizeFactor<double>('chat.fluffy.font_size_factor', 1.0),
hideRedactedEvents<bool>('chat.fluffy.hideRedactedEvents', false),
hideUnknownEvents<bool>('chat.fluffy.hideUnknownEvents', true),
separateChatTypes<bool>('chat.fluffy.separateChatTypes', false),
autoplayImages<bool>('chat.fluffy.autoplay_images', true),
sendTypingNotifications<bool>('chat.fluffy.send_typing_notifications', true),
sendPublicReadReceipts<bool>('chat.fluffy.send_public_read_receipts', true),
swipeRightToLeftToReply<bool>('chat.fluffy.swipeRightToLeftToReply', true),
sendOnEnter<bool>('chat.fluffy.send_on_enter', false),
showPresences<bool>('chat.fluffy.show_presences', true),
displayNavigationRail<bool>('chat.fluffy.display_navigation_rail', false),
experimentalVoip<bool>('chat.fluffy.experimental_voip', false),
shareKeysWith<String>('chat.fluffy.share_keys_with_2', 'all'),
noEncryptionWarningShown<bool>(
'chat.fluffy.no_encryption_warning_shown',
@ -61,40 +49,88 @@ enum AppSettings<T> {
'chat.fluffy.display_chat_details_column',
false,
),
// AppConfig-mirrored settings
applicationName<String>('chat.fluffy.application_name', 'FluffyChat'),
defaultHomeserver<String>('chat.fluffy.default_homeserver', 'matrix.org'),
privacyUrl<String>(
'chat.fluffy.privacy_url',
'https://github.com/krille-chan/fluffychat/blob/main/PRIVACY.md',
),
// colorSchemeSeed stored as ARGB int
colorSchemeSeedInt<int>(
'chat.fluffy.color_scheme_seed',
0xFF5625BA,
),
enableSoftLogout<bool>('chat.fluffy.enable_soft_logout', false);
final String key;
final T defaultValue;
const AppSettings(this.key, this.defaultValue);
static late final SharedPreferences store;
static Future<SharedPreferences> init({loadWebConfigFile = true}) async {
final store = AppSettings.store = await SharedPreferences.getInstance();
if (store.getBool(AppSettings.sendOnEnter.key) == null) {
await store.setBool(AppSettings.sendOnEnter.key, !PlatformInfos.isMobile);
}
if (kIsWeb && loadWebConfigFile) {
try {
final configJsonString =
utf8.decode((await http.get(Uri.parse('config.json'))).bodyBytes);
final configJson =
json.decode(configJsonString) as Map<String, Object?>;
for (final setting in AppSettings.values) {
if (store.get(setting.key) != null) continue;
final configValue = configJson[setting.key];
if (configValue == null) continue;
if (configValue is bool) {
await store.setBool(setting.key, configValue);
}
if (configValue is String) {
await store.setString(setting.key, configValue);
}
if (configValue is int) {
await store.setInt(setting.key, configValue);
}
if (configValue is double) {
await store.setDouble(setting.key, configValue);
}
}
} on FormatException catch (_) {
Logs().v('[ConfigLoader] config.json not found');
} catch (e) {
Logs().v('[ConfigLoader] config.json not found', e);
}
}
return store;
}
}
extension AppSettingsBoolExtension on AppSettings<bool> {
bool getItem(SharedPreferences store) => store.getBool(key) ?? defaultValue;
bool get value => AppSettings.store.getBool(key) ?? defaultValue;
Future<void> setItem(SharedPreferences store, bool value) =>
store.setBool(key, value);
Future<void> setItem(bool value) => AppSettings.store.setBool(key, value);
}
extension AppSettingsStringExtension on AppSettings<String> {
String getItem(SharedPreferences store) =>
store.getString(key) ?? defaultValue;
String get value => AppSettings.store.getString(key) ?? defaultValue;
Future<void> setItem(SharedPreferences store, String value) =>
store.setString(key, value);
Future<void> setItem(String value) => AppSettings.store.setString(key, value);
}
extension AppSettingsIntExtension on AppSettings<int> {
int getItem(SharedPreferences store) => store.getInt(key) ?? defaultValue;
int get value => AppSettings.store.getInt(key) ?? defaultValue;
Future<void> setItem(SharedPreferences store, int value) =>
store.setInt(key, value);
Future<void> setItem(int value) => AppSettings.store.setInt(key, value);
}
extension AppSettingsDoubleExtension on AppSettings<double> {
double getItem(SharedPreferences store) =>
store.getDouble(key) ?? defaultValue;
double get value => AppSettings.store.getDouble(key) ?? defaultValue;
Future<void> setItem(SharedPreferences store, double value) =>
store.setDouble(key, value);
Future<void> setItem(double value) => AppSettings.store.setDouble(key, value);
}

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'app_config.dart';
abstract class FluffyThemes {
@ -45,7 +46,7 @@ abstract class FluffyThemes {
]) {
final colorScheme = ColorScheme.fromSeed(
brightness: brightness,
seedColor: seed ?? AppConfig.colorSchemeSeed ?? AppConfig.primaryColor,
seedColor: seed ?? Color(AppSettings.colorSchemeSeedInt.value),
);
final isColumnMode = FluffyThemes.isColumnMode(context);
return ThemeData(

@ -6,7 +6,6 @@ import 'package:flutter_vodozemac/flutter_vodozemac.dart' as vod;
import 'package:matrix/matrix.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/utils/client_manager.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'config/setting_keys.dart';
@ -14,17 +13,17 @@ import 'utils/background_push.dart';
import 'widgets/fluffy_chat_app.dart';
void main() async {
Logs().i('Welcome to ${AppConfig.applicationName} <3');
// Our background push shared isolate accesses flutter-internal things very early in the startup proccess
// To make sure that the parts of flutter needed are started up already, we need to ensure that the
// widget bindings are initialized already.
WidgetsFlutterBinding.ensureInitialized();
final store = await AppSettings.init();
Logs().i('Welcome to ${AppSettings.applicationName.value} <3');
await vod.init(wasmPath: './assets/assets/vodozemac/');
Logs().nativeColors = !PlatformInfos.isIOS;
final store = await SharedPreferences.getInstance();
final clients = await ClientManager.getClients(store: store);
// If the app starts in detached mode, we assume that it is in
@ -44,14 +43,14 @@ void main() async {
// To start the flutter engine afterwards we add an custom observer.
WidgetsBinding.instance.addObserver(AppStarter(clients, store));
Logs().i(
'${AppConfig.applicationName} started in background-fetch mode. No GUI will be created unless the app is no longer detached.',
'${AppSettings.applicationName.value} started in background-fetch mode. No GUI will be created unless the app is no longer detached.',
);
return;
}
// Started in foreground mode.
Logs().i(
'${AppConfig.applicationName} started in foreground mode. Rendering GUI...',
'${AppSettings.applicationName.value} started in foreground mode. Rendering GUI...',
);
await startGui(clients, store);
}
@ -63,7 +62,7 @@ Future<void> startGui(List<Client> clients, SharedPreferences store) async {
if (PlatformInfos.isMobile) {
try {
pin =
await const FlutterSecureStorage().read(key: SettingKeys.appLockKey);
await const FlutterSecureStorage().read(key: 'chat.fluffy.app_lock');
} catch (e, s) {
Logs().d('Unable to read PIN from Secure storage', e, s);
}
@ -92,7 +91,7 @@ class AppStarter with WidgetsBindingObserver {
if (state == AppLifecycleState.detached) return;
Logs().i(
'${AppConfig.applicationName} switches from the detached background-fetch mode to ${state.name} mode. Rendering GUI...',
'${AppSettings.applicationName.value} switches from the detached background-fetch mode to ${state.name} mode. Rendering GUI...',
);
// Switching to foreground mode needs to reenable send online sync presence.
for (final client in clients) {

@ -13,10 +13,8 @@ import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
import 'package:matrix/matrix.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:universal_html/html.dart' as html;
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/l10n/l10n.dart';
@ -224,7 +222,7 @@ class ChatController extends State<ChatPageWithRoom>
}
void _loadDraft() async {
final prefs = await SharedPreferences.getInstance();
final prefs = Matrix.of(context).store;
final draft = prefs.getString('draft_$roomId');
if (draft != null && draft.isNotEmpty) {
sendController.text = draft;
@ -274,7 +272,7 @@ class ChatController extends State<ChatPageWithRoom>
KeyEventResult _customEnterKeyHandling(FocusNode node, KeyEvent evt) {
if (!HardwareKeyboard.instance.isShiftPressed &&
evt.logicalKey.keyLabel == 'Enter' &&
(AppConfig.sendOnEnter ?? !PlatformInfos.isMobile)) {
AppSettings.sendOnEnter.value) {
if (evt is KeyDownEvent) {
send();
}
@ -325,7 +323,7 @@ class ChatController extends State<ChatPageWithRoom>
WidgetsBinding.instance.addPostFrameCallback(_shareItems);
super.initState();
_displayChatDetailsColumn = ValueNotifier(
AppSettings.displayChatDetailsColumn.getItem(Matrix.of(context).store),
AppSettings.displayChatDetailsColumn.value,
);
sendingClient = Matrix.of(context).client;
@ -365,7 +363,9 @@ class ChatController extends State<ChatPageWithRoom>
var readMarkerEventIndex = readMarkerEventId.isEmpty
? -1
: timeline!.events
.filterByVisibleInGui(exceptionEventId: readMarkerEventId)
.filterByVisibleInGui(
exceptionEventId: readMarkerEventId,
)
.indexWhere((e) => e.eventId == readMarkerEventId);
// Read marker is existing but not found in first events. Try a single
@ -373,7 +373,9 @@ class ChatController extends State<ChatPageWithRoom>
if (readMarkerEventId.isNotEmpty && readMarkerEventIndex == -1) {
await timeline?.requestHistory(historyCount: _loadHistoryCount);
readMarkerEventIndex = timeline!.events
.filterByVisibleInGui(exceptionEventId: readMarkerEventId)
.filterByVisibleInGui(
exceptionEventId: readMarkerEventId,
)
.indexWhere((e) => e.eventId == readMarkerEventId);
}
@ -492,7 +494,7 @@ class ChatController extends State<ChatPageWithRoom>
_setReadMarkerFuture = timeline
.setReadMarker(
eventId: eventId,
public: AppConfig.sendPublicReadReceipts,
public: AppSettings.sendPublicReadReceipts.value,
)
.then((_) {
_setReadMarkerFuture = null;
@ -542,7 +544,7 @@ class ChatController extends State<ChatPageWithRoom>
Future<void> send() async {
if (sendController.text.trim().isEmpty) return;
_storeInputTimeoutTimer?.cancel();
final prefs = await SharedPreferences.getInstance();
final prefs = Matrix.of(context).store;
prefs.remove('draft_$roomId');
var parseCommands = true;
@ -960,7 +962,9 @@ class ChatController extends State<ChatPageWithRoom>
final eventIndex = foundEvent == null
? -1
: timeline!.events
.filterByVisibleInGui(exceptionEventId: eventId)
.filterByVisibleInGui(
exceptionEventId: eventId,
)
.indexOf(foundEvent);
if (eventIndex == -1) {
@ -1203,7 +1207,7 @@ class ChatController extends State<ChatPageWithRoom>
_storeInputTimeoutTimer?.cancel();
_storeInputTimeoutTimer = Timer(_storeInputTimeout, () async {
final prefs = await SharedPreferences.getInstance();
final prefs = Matrix.of(context).store;
await prefs.setString('draft_$roomId', text);
});
if (text.endsWith(' ') && Matrix.of(context).hasComplexBundles) {
@ -1220,7 +1224,7 @@ class ChatController extends State<ChatPageWithRoom>
}
}
}
if (AppConfig.sendTypingNotifications) {
if (AppSettings.sendTypingNotifications.value) {
typingCoolDown?.cancel();
typingCoolDown = Timer(const Duration(seconds: 2), () {
typingCoolDown = null;
@ -1307,7 +1311,6 @@ class ChatController extends State<ChatPageWithRoom>
void toggleDisplayChatDetailsColumn() async {
await AppSettings.displayChatDetailsColumn.setItem(
Matrix.of(context).store,
!_displayChatDetailsColumn.value,
);
_displayChatDetailsColumn.value = !_displayChatDetailsColumn.value;

@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
import 'package:animations/animations.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pages/chat/recording_input_row.dart';
import 'package:fluffychat/pages/chat/recording_view_model.dart';
@ -297,10 +297,11 @@ class ChatInputRow extends StatelessWidget {
maxLines: 8,
autofocus: !PlatformInfos.isMobile,
keyboardType: TextInputType.multiline,
textInputAction: AppConfig.sendOnEnter == true &&
PlatformInfos.isMobile
? TextInputAction.send
: null,
textInputAction:
AppSettings.sendOnEnter.value == true &&
PlatformInfos.isMobile
? TextInputAction.send
: null,
onSubmitted: controller.onInputBarSubmitted,
onSubmitImage: controller.sendImageFromClipBoard,
focusNode: controller.inputFocus,

@ -6,7 +6,7 @@ import 'package:badges/badges.dart';
import 'package:desktop_drop/desktop_drop.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pages/chat/chat.dart';
@ -119,7 +119,7 @@ class ChatView extends StatelessWidget {
];
} else if (!controller.room.isArchived) {
return [
if (AppConfig.experimentalVoip &&
if (AppSettings.experimentalVoip.value &&
Matrix.of(context).voipPlugin != null &&
controller.room.isDirectChat)
IconButton(

@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/l10n/l10n.dart';
class CuteContent extends StatefulWidget {
@ -21,7 +21,7 @@ class _CuteContentState extends State<CuteContent> {
@override
void initState() {
if (AppConfig.autoplayImages && !_isOverlayShown) {
if (AppSettings.autoplayImages.value && !_isOverlayShown) {
addOverlay();
}
super.initState();

@ -4,6 +4,7 @@ import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/pages/image_viewer/image_viewer.dart';
import 'package:fluffychat/utils/file_description.dart';
import 'package:fluffychat/utils/url_launcher.dart';
@ -139,14 +140,14 @@ class ImageBubble extends StatelessWidget {
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
style: TextStyle(
color: textColor,
fontSize:
AppConfig.fontSizeFactor * AppConfig.messageFontSize,
fontSize: AppSettings.fontSizeFactor.value *
AppConfig.messageFontSize,
),
options: const LinkifyOptions(humanize: false),
linkStyle: TextStyle(
color: linkColor,
fontSize:
AppConfig.fontSizeFactor * AppConfig.messageFontSize,
fontSize: AppSettings.fontSizeFactor.value *
AppConfig.messageFontSize,
decoration: TextDecoration.underline,
decorationColor: linkColor,
),

@ -7,6 +7,7 @@ import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
import 'package:matrix/matrix.dart';
import 'package:swipe_to_action/swipe_to_action.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pages/chat/events/room_creation_state_event.dart';
@ -204,7 +205,7 @@ class Message extends StatelessWidget {
child: Icon(Icons.check_outlined),
),
),
direction: AppConfig.swipeRightToLeftToReply
direction: AppSettings.swipeRightToLeftToReply.value
? SwipeDirection.endToStart
: SwipeDirection.startToEnd,
onSwipe: (_) => onSwipe(),
@ -243,7 +244,7 @@ class Message extends StatelessWidget {
child: Text(
event.originServerTs.localizedTime(context),
style: TextStyle(
fontSize: 12 * AppConfig.fontSizeFactor,
fontSize: 12 * AppSettings.fontSizeFactor.value,
fontWeight: FontWeight.bold,
color: theme.colorScheme.secondary,
),
@ -890,7 +891,7 @@ class Message extends StatelessWidget {
child: Text(
L10n.of(context).readUpToHere,
style: TextStyle(
fontSize: 12 * AppConfig.fontSizeFactor,
fontSize: 12 * AppSettings.fontSizeFactor.value,
),
),
),

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pages/chat/events/video_player.dart';
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
@ -105,7 +106,8 @@ class MessageContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
final fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor;
final fontSize =
AppConfig.messageFontSize * AppSettings.fontSizeFactor.value;
final buttonTextColor = textColor;
switch (event.type) {
case EventTypes.Message:
@ -255,7 +257,7 @@ class MessageContent extends StatelessWidget {
},
);
}
var html = AppConfig.renderHtml && event.isRichMessage
var html = AppSettings.renderHtml.value && event.isRichMessage
? event.formattedText
: event.body;
if (event.messageType == MessageTypes.Emote) {
@ -274,14 +276,14 @@ class MessageContent extends StatelessWidget {
html: html,
textColor: textColor,
room: event.room,
fontSize: AppConfig.fontSizeFactor *
fontSize: AppSettings.fontSizeFactor.value *
AppConfig.messageFontSize *
(bigEmotes ? 5 : 1),
limitHeight: !selected,
linkStyle: TextStyle(
color: linkColor,
fontSize:
AppConfig.fontSizeFactor * AppConfig.messageFontSize,
fontSize: AppSettings.fontSizeFactor.value *
AppConfig.messageFontSize,
decoration: TextDecoration.underline,
decorationColor: linkColor,
),

@ -4,6 +4,7 @@ import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/utils/file_description.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
import 'package:fluffychat/utils/url_launcher.dart';
@ -92,12 +93,14 @@ class MessageDownloadContent extends StatelessWidget {
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
style: TextStyle(
color: textColor,
fontSize: AppConfig.fontSizeFactor * AppConfig.messageFontSize,
fontSize: AppSettings.fontSizeFactor.value *
AppConfig.messageFontSize,
),
options: const LinkifyOptions(humanize: false),
linkStyle: TextStyle(
color: linkColor,
fontSize: AppConfig.fontSizeFactor * AppConfig.messageFontSize,
fontSize: AppSettings.fontSizeFactor.value *
AppConfig.messageFontSize,
decoration: TextDecoration.underline,
decorationColor: linkColor,
),

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import '../../../config/app_config.dart';
@ -30,7 +31,8 @@ class ReplyContent extends StatelessWidget {
final timeline = this.timeline;
final displayEvent =
timeline != null ? replyEvent.getDisplayEvent(timeline) : replyEvent;
final fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor;
final fontSize =
AppConfig.messageFontSize * AppSettings.fontSizeFactor.value;
final color = theme.brightness == Brightness.dark
? theme.colorScheme.onTertiaryContainer
: ownMessage

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
@ -68,7 +69,7 @@ class StateMessage extends StatelessWidget {
),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12 * AppConfig.fontSizeFactor,
fontSize: 12 * AppSettings.fontSizeFactor.value,
decoration: event.redacted
? TextDecoration.lineThrough
: null,

@ -6,6 +6,7 @@ import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/utils/file_description.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
import 'package:fluffychat/utils/platform_infos.dart';
@ -136,14 +137,14 @@ class EventVideoPlayer extends StatelessWidget {
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
style: TextStyle(
color: textColor,
fontSize:
AppConfig.fontSizeFactor * AppConfig.messageFontSize,
fontSize: AppSettings.fontSizeFactor.value *
AppConfig.messageFontSize,
),
options: const LinkifyOptions(humanize: false),
linkStyle: TextStyle(
color: linkColor,
fontSize:
AppConfig.fontSizeFactor * AppConfig.messageFontSize,
fontSize: AppSettings.fontSizeFactor.value *
AppConfig.messageFontSize,
decoration: TextDecoration.underline,
decorationColor: linkColor,
),

@ -415,8 +415,7 @@ class InputBar extends StatelessWidget {
// it sets the types for the callback incorrectly
onSubmitted!(text);
},
maxLength:
AppSettings.textMessageMaxLength.getItem(Matrix.of(context).store),
maxLength: AppSettings.textMessageMaxLength.value,
decoration: decoration,
onChanged: (text) {
// fix for the library for now

@ -15,7 +15,6 @@ import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'events/audio_player.dart';
class RecordingViewModel extends StatefulWidget {
@ -62,8 +61,6 @@ class RecordingViewModelState extends State<RecordingViewModel> {
}
if (await AudioRecorder().hasPermission() == false) return;
final store = Matrix.of(context).store;
final audioRecorder = _audioRecorder ??= AudioRecorder();
setState(() {});
@ -93,12 +90,12 @@ class RecordingViewModelState extends State<RecordingViewModel> {
await audioRecorder.start(
RecordConfig(
bitRate: AppSettings.audioRecordingBitRate.getItem(store),
sampleRate: AppSettings.audioRecordingSamplingRate.getItem(store),
numChannels: AppSettings.audioRecordingNumChannels.getItem(store),
autoGain: AppSettings.audioRecordingAutoGain.getItem(store),
echoCancel: AppSettings.audioRecordingEchoCancel.getItem(store),
noiseSuppress: AppSettings.audioRecordingNoiseSuppress.getItem(store),
bitRate: AppSettings.audioRecordingBitRate.value,
sampleRate: AppSettings.audioRecordingSamplingRate.value,
numChannels: AppSettings.audioRecordingNumChannels.value,
autoGain: AppSettings.audioRecordingAutoGain.value,
echoCancel: AppSettings.audioRecordingEchoCancel.value,
noiseSuppress: AppSettings.audioRecordingNoiseSuppress.value,
encoder: codec,
),
path: path ?? '',

@ -12,7 +12,6 @@ import 'package:matrix/matrix.dart' as sdk;
import 'package:matrix/matrix.dart';
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pages/chat_list/chat_list_view.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart';
@ -94,9 +93,7 @@ class ChatListController extends State<ChatList>
StreamSubscription? _intentUriStreamSubscription;
ActiveFilter activeFilter = AppConfig.separateChatTypes
? ActiveFilter.messages
: ActiveFilter.allChats;
late ActiveFilter activeFilter;
String? _activeSpaceId;
String? get activeSpaceId => _activeSpaceId;
@ -401,6 +398,9 @@ class ChatListController extends State<ChatList>
@override
void initState() {
activeFilter = AppSettings.separateChatTypes.value
? ActiveFilter.messages
: ActiveFilter.allChats;
_initReceiveSharingIntent();
_activeSpaceId = widget.activeSpace;
@ -713,8 +713,7 @@ class ChatListController extends State<ChatList>
context: context,
);
if (result == OkCancelResult.ok) {
await Matrix.of(context).store.setBool(SettingKeys.showPresences, false);
AppConfig.showPresences = false;
AppSettings.showPresences.setItem(false);
setState(() {});
}
}

@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pages/chat_list/chat_list.dart';
import 'package:fluffychat/pages/chat_list/chat_list_item.dart';
@ -120,7 +120,8 @@ class ChatListViewBody extends StatelessWidget {
),
),
],
if (!controller.isSearchMode && AppConfig.showPresences)
if (!controller.isSearchMode &&
AppSettings.showPresences.value)
GestureDetector(
onLongPress: () => controller.dismissStatusList(),
child: StatusMessageList(
@ -155,14 +156,14 @@ class ChatListViewBody extends StatelessWidget {
shrinkWrap: true,
scrollDirection: Axis.horizontal,
children: [
if (AppConfig.separateChatTypes)
if (AppSettings.separateChatTypes.value)
ActiveFilter.messages
else
ActiveFilter.allChats,
ActiveFilter.groups,
ActiveFilter.unread,
if (spaceDelegateCandidates.isNotEmpty &&
!AppConfig.displayNavigationRail &&
!AppSettings.displayNavigationRail.value &&
!FluffyThemes.isColumnMode(context))
ActiveFilter.spaces,
]

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pages/chat_list/chat_list.dart';
@ -32,7 +32,7 @@ class ChatListView extends StatelessWidget {
child: Row(
children: [
if (FluffyThemes.isColumnMode(context) ||
AppConfig.displayNavigationRail) ...[
AppSettings.displayNavigationRail.value) ...[
SpacesNavigationRail(
activeSpaceId: controller.activeSpaceId,
onGoToChats: controller.clearActiveSpace,

@ -11,6 +11,7 @@ import 'package:universal_html/html.dart' as html;
import 'package:url_launcher/url_launcher_string.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pages/homeserver_picker/homeserver_picker_view.dart';
import 'package:fluffychat/utils/file_selector.dart';
@ -34,7 +35,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
bool isLoading = false;
final TextEditingController homeserverController = TextEditingController(
text: AppConfig.defaultHomeserver,
text: AppSettings.defaultHomeserver.value,
);
String? error;
@ -211,7 +212,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
case MoreLoginActions.importBackup:
restoreBackup();
case MoreLoginActions.privacy:
launchUrlString(AppConfig.privacyUrl);
launchUrlString(AppSettings.privacyUrl.value);
case MoreLoginActions.about:
PlatformInfos.showDialog(context);
}

@ -5,6 +5,7 @@ import 'package:url_launcher/url_launcher.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart';
import 'package:fluffychat/widgets/layouts/login_scaffold.dart';
@ -155,7 +156,7 @@ class HomeserverPickerView extends StatelessWidget {
AppConfig.borderRadius,
),
),
hintText: AppConfig.defaultHomeserver,
hintText: AppSettings.defaultHomeserver.value,
hintStyle: TextStyle(
color: theme.colorScheme.surfaceTint,
),

@ -4,7 +4,7 @@ import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/utils/fluffy_share.dart';
@ -244,7 +244,7 @@ class SettingsView extends StatelessWidget {
ListTile(
leading: const Icon(Icons.privacy_tip_outlined),
title: Text(L10n.of(context).privacy),
onTap: () => launchUrlString(AppConfig.privacyUrl),
onTap: () => launchUrlString(AppSettings.privacyUrl.value),
),
ListTile(
leading: const Icon(Icons.info_outline_rounded),

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/l10n/l10n.dart';
@ -34,41 +33,29 @@ class SettingsChatView extends StatelessWidget {
SettingsSwitchListTile.adaptive(
title: L10n.of(context).formattedMessages,
subtitle: L10n.of(context).formattedMessagesDescription,
onChanged: (b) => AppConfig.renderHtml = b,
storeKey: SettingKeys.renderHtml,
defaultValue: AppConfig.renderHtml,
setting: AppSettings.renderHtml,
),
SettingsSwitchListTile.adaptive(
title: L10n.of(context).hideRedactedMessages,
subtitle: L10n.of(context).hideRedactedMessagesBody,
onChanged: (b) => AppConfig.hideRedactedEvents = b,
storeKey: SettingKeys.hideRedactedEvents,
defaultValue: AppConfig.hideRedactedEvents,
setting: AppSettings.hideRedactedEvents,
),
SettingsSwitchListTile.adaptive(
title: L10n.of(context).hideInvalidOrUnknownMessageFormats,
onChanged: (b) => AppConfig.hideUnknownEvents = b,
storeKey: SettingKeys.hideUnknownEvents,
defaultValue: AppConfig.hideUnknownEvents,
setting: AppSettings.hideUnknownEvents,
),
if (PlatformInfos.isMobile)
SettingsSwitchListTile.adaptive(
title: L10n.of(context).autoplayImages,
onChanged: (b) => AppConfig.autoplayImages = b,
storeKey: SettingKeys.autoplayImages,
defaultValue: AppConfig.autoplayImages,
setting: AppSettings.autoplayImages,
),
SettingsSwitchListTile.adaptive(
title: L10n.of(context).sendOnEnter,
onChanged: (b) => AppConfig.sendOnEnter = b,
storeKey: SettingKeys.sendOnEnter,
defaultValue: AppConfig.sendOnEnter ?? !PlatformInfos.isMobile,
setting: AppSettings.sendOnEnter,
),
SettingsSwitchListTile.adaptive(
title: L10n.of(context).swipeRightToLeftToReply,
onChanged: (b) => AppConfig.swipeRightToLeftToReply = b,
storeKey: SettingKeys.swipeRightToLeftToReply,
defaultValue: AppConfig.swipeRightToLeftToReply,
setting: AppSettings.swipeRightToLeftToReply,
),
Divider(color: theme.dividerColor),
ListTile(
@ -102,12 +89,10 @@ class SettingsChatView extends StatelessWidget {
SettingsSwitchListTile.adaptive(
title: L10n.of(context).experimentalVideoCalls,
onChanged: (b) {
AppConfig.experimentalVoip = b;
Matrix.of(context).createVoipPlugin();
return;
},
storeKey: SettingKeys.experimentalVoip,
defaultValue: AppConfig.experimentalVoip,
setting: AppSettings.experimentalVoip,
),
],
),

@ -112,7 +112,6 @@ class SettingsSecurityController extends State<SettingsSecurity> {
void changeShareKeysWith(ShareKeysWith? shareKeysWith) async {
if (shareKeysWith == null) return;
AppSettings.shareKeysWith.setItem(
Matrix.of(context).store,
shareKeysWith.name,
);
Matrix.of(context).client.shareKeysWith = shareKeysWith;

@ -63,16 +63,12 @@ class SettingsSecurityView extends StatelessWidget {
title: L10n.of(context).sendTypingNotifications,
subtitle:
L10n.of(context).sendTypingNotificationsDescription,
onChanged: (b) => AppConfig.sendTypingNotifications = b,
storeKey: SettingKeys.sendTypingNotifications,
defaultValue: AppConfig.sendTypingNotifications,
setting: AppSettings.sendTypingNotifications,
),
SettingsSwitchListTile.adaptive(
title: L10n.of(context).sendReadReceipts,
subtitle: L10n.of(context).sendReadReceiptsDescription,
onChanged: (b) => AppConfig.sendPublicReadReceipts = b,
storeKey: SettingKeys.sendPublicReadReceipts,
defaultValue: AppConfig.sendPublicReadReceipts,
setting: AppSettings.sendPublicReadReceipts,
),
ListTile(
trailing: const Icon(Icons.chevron_right_outlined),

@ -18,7 +18,9 @@ class SettingsStyle extends StatefulWidget {
class SettingsStyleController extends State<SettingsStyle> {
void setChatColor(Color? color) async {
AppConfig.colorSchemeSeed = color;
AppSettings.colorSchemeSeedInt.setItem(
color?.toARGB32() ?? AppSettings.colorSchemeSeedInt.defaultValue,
);
ThemeController.of(context).setPrimaryColor(color);
}
@ -156,11 +158,7 @@ class SettingsStyleController extends State<SettingsStyle> {
}
void changeFontSizeFactor(double d) {
setState(() => AppConfig.fontSizeFactor = d);
Matrix.of(context).store.setString(
SettingKeys.fontSizeFactor,
AppConfig.fontSizeFactor.toString(),
);
AppSettings.fontSizeFactor.setItem(d);
}
@override

@ -230,7 +230,7 @@ class SettingsStyleView extends StatelessWidget {
style: TextStyle(
color: theme.onBubbleColor,
fontSize: AppConfig.messageFontSize *
AppConfig.fontSizeFactor,
AppSettings.fontSizeFactor.value,
),
),
),
@ -263,7 +263,7 @@ class SettingsStyleView extends StatelessWidget {
style: TextStyle(
color: theme.colorScheme.onSurface,
fontSize: AppConfig.messageFontSize *
AppConfig.fontSizeFactor,
AppSettings.fontSizeFactor.value,
),
),
),
@ -325,13 +325,15 @@ class SettingsStyleView extends StatelessWidget {
),
ListTile(
title: Text(L10n.of(context).fontSize),
trailing: Text('× ${AppConfig.fontSizeFactor}'),
trailing: Text(
'× ${AppSettings.fontSizeFactor.value}',
),
),
Slider.adaptive(
min: 0.5,
max: 2.5,
divisions: 20,
value: AppConfig.fontSizeFactor,
value: AppSettings.fontSizeFactor.value,
semanticFormatterCallback: (d) => d.toString(),
onChanged: controller.changeFontSizeFactor,
),
@ -349,21 +351,15 @@ class SettingsStyleView extends StatelessWidget {
),
SettingsSwitchListTile.adaptive(
title: L10n.of(context).presencesToggle,
onChanged: (b) => AppConfig.showPresences = b,
storeKey: SettingKeys.showPresences,
defaultValue: AppConfig.showPresences,
setting: AppSettings.showPresences,
),
SettingsSwitchListTile.adaptive(
title: L10n.of(context).separateChatTypes,
onChanged: (b) => AppConfig.separateChatTypes = b,
storeKey: SettingKeys.separateChatTypes,
defaultValue: AppConfig.separateChatTypes,
setting: AppSettings.separateChatTypes,
),
SettingsSwitchListTile.adaptive(
title: L10n.of(context).displayNavigationRail,
onChanged: (b) => AppConfig.displayNavigationRail = b,
storeKey: SettingKeys.displayNavigationRail,
defaultValue: AppConfig.displayNavigationRail,
setting: AppSettings.displayNavigationRail,
),
],
),

@ -217,8 +217,7 @@ class BackgroundPush {
currentPushers.first.lang == 'en' &&
currentPushers.first.data.url.toString() == gatewayUrl &&
currentPushers.first.data.format ==
AppSettings.pushNotificationsPusherFormat
.getItem(matrix!.store) &&
AppSettings.pushNotificationsPusherFormat.value &&
mapEquals(
currentPushers.single.data.additionalProperties,
{"data_message": pusherDataMessageFormat},
@ -258,8 +257,7 @@ class BackgroundPush {
lang: 'en',
data: PusherData(
url: Uri.parse(gatewayUrl!),
format: AppSettings.pushNotificationsPusherFormat
.getItem(matrix!.store),
format: AppSettings.pushNotificationsPusherFormat.value,
additionalProperties: {"data_message": pusherDataMessageFormat},
),
kind: 'http',
@ -325,7 +323,7 @@ class BackgroundPush {
if (matrix == null) {
return;
}
if ((matrix?.store.getBool(SettingKeys.showNoGoogle) ?? false) == true) {
if (!AppSettings.showNoGoogle.value) {
return;
}
await loadLocale();
@ -356,8 +354,7 @@ class BackgroundPush {
}
}
await setupPusher(
gatewayUrl:
AppSettings.pushNotificationsGatewayUrl.getItem(matrix!.store),
gatewayUrl: AppSettings.pushNotificationsGatewayUrl.value,
token: _fcmToken,
);
}
@ -414,18 +411,18 @@ class BackgroundPush {
oldTokens: oldTokens,
useDeviceSpecificAppId: true,
);
await matrix?.store.setString(SettingKeys.unifiedPushEndpoint, newEndpoint);
await matrix?.store.setBool(SettingKeys.unifiedPushRegistered, true);
await AppSettings.unifiedPushEndpoint.setItem(newEndpoint);
await AppSettings.unifiedPushRegistered.setItem(true);
}
Future<void> _upUnregistered(String i) async {
upAction = true;
Logs().i('[Push] Removing UnifiedPush endpoint...');
final oldEndpoint =
matrix?.store.getString(SettingKeys.unifiedPushEndpoint);
await matrix?.store.setBool(SettingKeys.unifiedPushRegistered, false);
await matrix?.store.remove(SettingKeys.unifiedPushEndpoint);
if (oldEndpoint?.isNotEmpty ?? false) {
final oldEndpoint = AppSettings.unifiedPushEndpoint.value;
await AppSettings.unifiedPushEndpoint
.setItem(AppSettings.unifiedPushEndpoint.defaultValue);
await AppSettings.unifiedPushRegistered.setItem(false);
if (oldEndpoint.isNotEmpty) {
// remove the old pusher
await setupPusher(
oldTokens: {oldEndpoint},

@ -11,7 +11,6 @@ import 'package:matrix/matrix.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:universal_html/html.dart' as html;
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/utils/custom_http_client.dart';
@ -101,8 +100,8 @@ abstract class ClientManager {
String clientName,
SharedPreferences store,
) async {
final shareKeysWith = AppSettings.shareKeysWith.getItem(store);
final enableSoftLogout = AppSettings.enableSoftLogout.getItem(store);
final shareKeysWith = AppSettings.shareKeysWith.value;
final enableSoftLogout = AppSettings.enableSoftLogout.value;
return Client(
clientName,
@ -147,7 +146,7 @@ abstract class ClientManager {
await NotificationsClient().notify(
title,
body: body,
appName: AppConfig.applicationName,
appName: AppSettings.applicationName.value,
hints: [
NotificationHint.soundName('message-new-instant'),
],

@ -5,6 +5,7 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/utils/client_manager.dart';
import 'package:fluffychat/utils/platform_infos.dart';
@ -57,13 +58,13 @@ extension InitWithRestoreExtension on Client {
? const FlutterSecureStorage()
: null;
await storage?.delete(
key: '${AppConfig.applicationName}_session_backup_$clientName',
key: '${AppSettings.applicationName.value}_session_backup_$clientName',
);
}
Future<void> initWithRestore({void Function()? onMigration}) async {
final storageKey =
'${AppConfig.applicationName}_session_backup_$clientName';
'${AppSettings.applicationName.value}_session_backup_$clientName';
final storage = PlatformInfos.isMobile || PlatformInfos.isLinux
? const FlutterSecureStorage()
: null;

@ -1,9 +1,12 @@
import 'package:matrix/matrix.dart';
import '../../config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
extension VisibleInGuiExtension on List<Event> {
List<Event> filterByVisibleInGui({String? exceptionEventId}) => where(
List<Event> filterByVisibleInGui({
String? exceptionEventId,
}) =>
where(
(event) => event.isVisibleInGui || event.eventId == exceptionEventId,
).toList();
}
@ -19,9 +22,9 @@ extension IsStateExtension on Event {
// if a reaction has been redacted we also want it to be hidden in the timeline
!{EventTypes.Reaction, EventTypes.Redaction}.contains(type) &&
// if we enabled to hide all redacted events, don't show those
(!AppConfig.hideRedactedEvents || !redacted) &&
(!AppSettings.hideRedactedEvents.value || !redacted) &&
// if we enabled to hide all unknown events, don't show those
(!AppConfig.hideUnknownEvents || isEventTypeKnown);
(!AppSettings.hideUnknownEvents.value || isEventTypeKnown);
bool get isState => !{
EventTypes.Message,

@ -6,7 +6,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:matrix/matrix.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/l10n/l10n.dart';
@ -52,8 +51,7 @@ Future<String?> getDatabaseCipher() async {
}
void _sendNoEncryptionWarning(Object exception) async {
final store = await SharedPreferences.getInstance();
final isStored = AppSettings.noEncryptionWarningShown.getItem(store);
final isStored = AppSettings.noEncryptionWarningShown.value;
if (isStored == true) return;
@ -63,5 +61,5 @@ void _sendNoEncryptionWarning(Object exception) async {
exception.toString(),
);
await AppSettings.noEncryptionWarningShown.setItem(store, true);
await AppSettings.noEncryptionWarningShown.setItem(true);
}

@ -6,7 +6,6 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_vodozemac/flutter_vodozemac.dart' as vod;
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/utils/client_download_content_extension.dart';
@ -59,7 +58,7 @@ void notificationTapBackground(
await vod.init();
_vodInitialized = true;
}
final store = await SharedPreferences.getInstance();
final store = await AppSettings.init();
final client = (await ClientManager.getClients(
initialize: false,
store: store,
@ -71,10 +70,6 @@ void notificationTapBackground(
waitUntilLoadCompletedLoaded: false,
);
AppConfig.sendPublicReadReceipts =
store.getBool(SettingKeys.sendPublicReadReceipts) ??
AppConfig.sendPublicReadReceipts;
if (!client.isLogged()) {
throw Exception('Notification tab in background but not logged in!');
}
@ -145,7 +140,7 @@ Future<void> notificationTap(
await room.setReadMarker(
payload.eventId ?? room.lastEvent!.eventId,
mRead: payload.eventId ?? room.lastEvent!.eventId,
public: AppConfig.sendPublicReadReceipts,
public: AppSettings.sendPublicReadReceipts.value,
);
case FluffyChatNotificationActions.reply:
final input = notificationResponse.input;

@ -7,6 +7,7 @@ import 'package:go_router/go_router.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/l10n/l10n.dart';
import '../config/app_config.dart';
@ -36,7 +37,7 @@ abstract class PlatformInfos {
static bool get platformCanRecord => (isMobile || isMacOS);
static String get clientName =>
'${AppConfig.applicationName} ${isWeb ? 'web' : Platform.operatingSystem}${kReleaseMode ? '' : 'Debug'}';
'${AppSettings.applicationName.value} ${isWeb ? 'web' : Platform.operatingSystem}${kReleaseMode ? '' : 'Debug'}';
static Future<String> getVersion() async {
var version = kIsWeb ? 'Web' : 'Unknown';
@ -88,7 +89,7 @@ abstract class PlatformInfos {
height: 64,
filterQuality: FilterQuality.medium,
),
applicationName: AppConfig.applicationName,
applicationName: AppSettings.applicationName.value,
);
}
}

@ -8,9 +8,9 @@ import 'package:collection/collection.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_shortcuts_new/flutter_shortcuts_new.dart';
import 'package:matrix/matrix.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/utils/client_download_content_extension.dart';
import 'package:fluffychat/utils/client_manager.dart';
@ -50,7 +50,7 @@ Future<void> pushHelper(
l10n.incomingMessages,
number: notification.counts?.unread,
ticker: l10n.unreadChatsInApp(
AppConfig.applicationName,
AppSettings.applicationName.value,
(notification.counts?.unread ?? 0).toString(),
),
importance: Importance.high,
@ -85,7 +85,7 @@ Future<void> _tryPushHelper(
client ??= (await ClientManager.getClients(
initialize: false,
store: await SharedPreferences.getInstance(),
store: await AppSettings.init(),
))
.first;
final event = await client.getEventByPushNotification(

@ -4,7 +4,6 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:matrix/matrix.dart';
import 'package:provider/provider.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/widgets/lock_screen.dart';
class AppLockWidget extends StatefulWidget {
@ -65,7 +64,7 @@ class AppLock extends State<AppLockWidget> with WidgetsBindingObserver {
Future<void> changePincode(String? pincode) async {
await const FlutterSecureStorage().write(
key: SettingKeys.appLockKey,
key: 'chat.fluffy.app_lock',
value: pincode,
);
_pincode = pincode;

@ -21,7 +21,7 @@ class _ConfigViewerState extends State<ConfigViewer> {
String initialValue,
) async {
if (appSetting is AppSettings<bool>) {
await appSetting.setItem(store, !(initialValue == 'true'));
await appSetting.setItem(!(initialValue == 'true'));
setState(() {});
return;
}
@ -35,13 +35,13 @@ class _ConfigViewerState extends State<ConfigViewer> {
if (value == null) return;
if (appSetting is AppSettings<String>) {
await appSetting.setItem(store, value);
await appSetting.setItem(value);
}
if (appSetting is AppSettings<int>) {
await appSetting.setItem(store, int.parse(value));
await appSetting.setItem(int.parse(value));
}
if (appSetting is AppSettings<double>) {
await appSetting.setItem(store, double.parse(value));
await appSetting.setItem(double.parse(value));
}
setState(() {});
@ -78,16 +78,16 @@ class _ConfigViewerState extends State<ConfigViewer> {
final appSetting = AppSettings.values[i];
var value = '';
if (appSetting is AppSettings<String>) {
value = appSetting.getItem(store);
value = appSetting.value;
}
if (appSetting is AppSettings<int>) {
value = appSetting.getItem(store).toString();
value = appSetting.value.toString();
}
if (appSetting is AppSettings<bool>) {
value = appSetting.getItem(store).toString();
value = appSetting.value.toString();
}
if (appSetting is AppSettings<double>) {
value = appSetting.getItem(store).toString();
value = appSetting.value.toString();
}
return ListTile(
title: Text(appSetting.name),

@ -5,11 +5,11 @@ import 'package:matrix/matrix.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:fluffychat/config/routes.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/widgets/app_lock.dart';
import 'package:fluffychat/widgets/theme_builder.dart';
import '../config/app_config.dart';
import '../utils/custom_scroll_behaviour.dart';
import 'matrix.dart';
@ -43,7 +43,7 @@ class FluffyChatApp extends StatelessWidget {
Widget build(BuildContext context) {
return ThemeBuilder(
builder: (context, themeMode, primaryColor) => MaterialApp.router(
title: AppConfig.applicationName,
title: AppSettings.applicationName.value,
themeMode: themeMode,
theme: FluffyThemes.buildTheme(context, Brightness.light, primaryColor),
darkTheme:

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/utils/platform_infos.dart';
@ -107,7 +108,7 @@ class _PrivacyButtons extends StatelessWidget {
),
),
TextButton(
onPressed: () => launchUrlString(AppConfig.privacyUrl),
onPressed: () => launchUrlString(AppSettings.privacyUrl.value),
child: Text(
L10n.of(context).privacy,
style: shadowTextStyle,

@ -9,7 +9,7 @@ import 'package:image/image.dart';
import 'package:matrix/matrix.dart';
import 'package:universal_html/html.dart' as html;
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/utils/client_download_content_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
@ -114,7 +114,7 @@ extension LocalNotificationsExtension on MatrixState {
title,
body: body,
replacesId: linuxNotificationIds[roomId] ?? 0,
appName: AppConfig.applicationName,
appName: AppSettings.applicationName.value,
appIcon: 'fluffychat',
actions: [
NotificationAction(
@ -139,7 +139,7 @@ extension LocalNotificationsExtension on MatrixState {
event.room.setReadMarker(
event.eventId,
mRead: event.eventId,
public: AppConfig.sendPublicReadReceipts,
public: AppSettings.sendPublicReadReceipts.value,
);
break;
case DesktopNotificationActions.openChat:

@ -6,7 +6,6 @@ import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:desktop_notifications/desktop_notifications.dart';
import 'package:http/http.dart' as http;
import 'package:image_picker/image_picker.dart';
import 'package:intl/intl.dart';
import 'package:just_audio/just_audio.dart';
@ -27,7 +26,6 @@ import 'package:fluffychat/utils/voip_plugin.dart';
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
import 'package:fluffychat/widgets/fluffy_chat_app.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
import '../config/app_config.dart';
import '../config/setting_keys.dart';
import '../pages/key_verification/key_verification_dialog.dart';
import '../utils/account_bundles.dart';
@ -155,7 +153,7 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
}
final candidate =
_loginClientCandidate ??= await ClientManager.createClient(
'${AppConfig.applicationName}-${DateTime.now().millisecondsSinceEpoch}',
'${AppSettings.applicationName.value}-${DateTime.now().millisecondsSinceEpoch}',
store,
)
..onLoginStateChanged
@ -221,24 +219,6 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
super.initState();
WidgetsBinding.instance.addObserver(this);
initMatrix();
if (PlatformInfos.isWeb) {
initConfig().then((_) => initSettings());
} else {
initSettings();
}
}
Future<void> initConfig() async {
try {
final configJsonString =
utf8.decode((await http.get(Uri.parse('config.json'))).bodyBytes);
final configJson = json.decode(configJsonString);
AppConfig.loadFromJson(configJson);
} on FormatException catch (_) {
Logs().v('[ConfigLoader] config.json not found');
} catch (e) {
Logs().v('[ConfigLoader] config.json not found', e);
}
}
void _registerSubs(String name) {
@ -358,7 +338,7 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
);
}
if (result == OkCancelResult.cancel) {
await store.setBool(SettingKeys.showNoGoogle, true);
await AppSettings.showNoGoogle.setItem(true);
}
},
);
@ -368,7 +348,7 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
}
void createVoipPlugin() async {
if (store.getBool(SettingKeys.experimentalVoip) == false) {
if (AppSettings.experimentalVoip.value) {
voipPlugin = null;
return;
}
@ -390,55 +370,6 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
}
}
void initSettings() {
AppConfig.fontSizeFactor =
double.tryParse(store.getString(SettingKeys.fontSizeFactor) ?? '') ??
AppConfig.fontSizeFactor;
AppConfig.renderHtml =
store.getBool(SettingKeys.renderHtml) ?? AppConfig.renderHtml;
AppConfig.swipeRightToLeftToReply =
store.getBool(SettingKeys.swipeRightToLeftToReply) ??
AppConfig.swipeRightToLeftToReply;
AppConfig.hideRedactedEvents =
store.getBool(SettingKeys.hideRedactedEvents) ??
AppConfig.hideRedactedEvents;
AppConfig.hideUnknownEvents =
store.getBool(SettingKeys.hideUnknownEvents) ??
AppConfig.hideUnknownEvents;
AppConfig.separateChatTypes =
store.getBool(SettingKeys.separateChatTypes) ??
AppConfig.separateChatTypes;
AppConfig.autoplayImages =
store.getBool(SettingKeys.autoplayImages) ?? AppConfig.autoplayImages;
AppConfig.sendTypingNotifications =
store.getBool(SettingKeys.sendTypingNotifications) ??
AppConfig.sendTypingNotifications;
AppConfig.sendPublicReadReceipts =
store.getBool(SettingKeys.sendPublicReadReceipts) ??
AppConfig.sendPublicReadReceipts;
AppConfig.sendOnEnter =
store.getBool(SettingKeys.sendOnEnter) ?? AppConfig.sendOnEnter;
AppConfig.experimentalVoip = store.getBool(SettingKeys.experimentalVoip) ??
AppConfig.experimentalVoip;
AppConfig.showPresences =
store.getBool(SettingKeys.showPresences) ?? AppConfig.showPresences;
AppConfig.displayNavigationRail =
store.getBool(SettingKeys.displayNavigationRail) ??
AppConfig.displayNavigationRail;
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);

@ -1,18 +1,16 @@
import 'package:flutter/material.dart';
import 'matrix.dart';
import 'package:fluffychat/config/setting_keys.dart';
class SettingsSwitchListTile extends StatefulWidget {
final bool defaultValue;
final String storeKey;
final AppSettings<bool> setting;
final String title;
final String? subtitle;
final Function(bool)? onChanged;
const SettingsSwitchListTile.adaptive({
super.key,
this.defaultValue = false,
required this.storeKey,
required this.setting,
required this.title,
this.subtitle,
this.onChanged,
@ -27,13 +25,12 @@ class SettingsSwitchListTileState extends State<SettingsSwitchListTile> {
Widget build(BuildContext context) {
final subtitle = widget.subtitle;
return SwitchListTile.adaptive(
value: Matrix.of(context).store.getBool(widget.storeKey) ??
widget.defaultValue,
value: widget.setting.value,
title: Text(widget.title),
subtitle: subtitle == null ? null : Text(subtitle),
onChanged: (bool newValue) async {
widget.onChanged?.call(newValue);
await Matrix.of(context).store.setBool(widget.storeKey, newValue);
await widget.setting.setItem(newValue);
setState(() {});
},
);

Loading…
Cancel
Save