diff --git a/lib/components/html_message.dart b/lib/components/html_message.dart index a67377a0..9139cf0b 100644 --- a/lib/components/html_message.dart +++ b/lib/components/html_message.dart @@ -2,6 +2,7 @@ import 'package:famedlysdk/famedlysdk.dart'; import 'package:flutter_matrix_html/flutter_html.dart'; import 'package:flutter/material.dart'; import '../utils/url_launcher.dart'; +import '../config/setting_keys.dart'; import 'matrix.dart'; @@ -59,10 +60,10 @@ class HtmlMessage extends StatelessWidget { ); }, setCodeLanguage: (String key, String value) async { - await matrix.store.setItem('code_language.$key', value); + await matrix.store.setItem('${SettingKeys.codeLanguage}.$key', value); }, getCodeLanguage: (String key) async { - return await matrix.store.getItem('code_language.$key'); + return await matrix.store.getItem('${SettingKeys.codeLanguage}.$key'); }, getPillInfo: (String identifier) async { if (room == null) { diff --git a/lib/components/list_items/message.dart b/lib/components/list_items/message.dart index 145e46de..5e99962d 100644 --- a/lib/components/list_items/message.dart +++ b/lib/components/list_items/message.dart @@ -72,9 +72,6 @@ class Message extends StatelessWidget { @override Widget build(BuildContext context) { - if (event.type == EventTypes.Unknown) { - return Container(); - } if (![EventTypes.Message, EventTypes.Sticker, EventTypes.Encrypted] .contains(event.type)) { return StateMessage(event); diff --git a/lib/components/matrix.dart b/lib/components/matrix.dart index 4f359959..c895daa1 100644 --- a/lib/components/matrix.dart +++ b/lib/components/matrix.dart @@ -23,6 +23,8 @@ import '../utils/beautify_string_extension.dart'; import '../utils/famedlysdk_store.dart'; import '../views/key_verification.dart'; import '../utils/platform_infos.dart'; +import '../config/app_config.dart'; +import '../config/setting_keys.dart'; import 'avatar.dart'; class Matrix extends StatefulWidget { @@ -72,7 +74,6 @@ class MatrixState extends State { String activeRoomId; File wallpaper; - bool renderHtml = false; String jitsiInstance = 'https://meet.jit.si/'; @@ -304,18 +305,26 @@ class MatrixState extends State { } if (store != null) { store - .getItem('chat.fluffy.jitsi_instance') + .getItem(SettingKeys.jitsiInstance) .then((final instance) => jitsiInstance = instance ?? jitsiInstance); - store.getItem('chat.fluffy.wallpaper').then((final path) async { + store.getItem(SettingKeys.wallpaper).then((final path) async { if (path == null) return; final file = File(path); if (await file.exists()) { wallpaper = file; } }); - store.getItem('chat.fluffy.renderHtml').then((final render) async { - renderHtml = render == '1'; - }); + store + .getItemBool(SettingKeys.renderHtml, AppConfig.renderHtml) + .then((value) => AppConfig.renderHtml = value); + store + .getItemBool( + SettingKeys.hideRedactedEvents, AppConfig.hideRedactedEvents) + .then((value) => AppConfig.hideRedactedEvents = value); + store + .getItemBool( + SettingKeys.hideUnknownEvents, AppConfig.hideUnknownEvents) + .then((value) => AppConfig.hideUnknownEvents = value); } if (kIsWeb) { onFocusSub = html.window.onFocus.listen((_) => webHasFocus = true); diff --git a/lib/components/message_content.dart b/lib/components/message_content.dart index 3909a764..a28c15c3 100644 --- a/lib/components/message_content.dart +++ b/lib/components/message_content.dart @@ -9,6 +9,7 @@ import 'package:matrix_link_text/link_text.dart'; import 'package:url_launcher/url_launcher.dart'; import '../utils/url_launcher.dart'; +import '../config/app_config.dart'; import 'html_message.dart'; import 'matrix.dart'; import 'message_download_content.dart'; @@ -43,7 +44,7 @@ class MessageContent extends StatelessWidget { case MessageTypes.Text: case MessageTypes.Notice: case MessageTypes.Emote: - if (Matrix.of(context).renderHtml && + if (AppConfig.renderHtml && !event.redacted && event.isRichMessage) { String html = event.content['formatted_body']; diff --git a/lib/components/reply_content.dart b/lib/components/reply_content.dart index 1ebc3b0f..bf3651eb 100644 --- a/lib/components/reply_content.dart +++ b/lib/components/reply_content.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'html_message.dart'; -import 'matrix.dart'; +import '../config/app_config.dart'; class ReplyContent extends StatelessWidget { final Event replyEvent; @@ -22,7 +22,7 @@ class ReplyContent extends StatelessWidget { ? replyEvent.getDisplayEvent(timeline) : replyEvent; if (displayEvent != null && - Matrix.of(context).renderHtml && + AppConfig.renderHtml && [EventTypes.Message, EventTypes.Encrypted] .contains(displayEvent.type) && [MessageTypes.Text, MessageTypes.Notice, MessageTypes.Emote] diff --git a/lib/components/theme_switcher.dart b/lib/components/theme_switcher.dart index dafdd4cc..1284d758 100644 --- a/lib/components/theme_switcher.dart +++ b/lib/components/theme_switcher.dart @@ -1,6 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'matrix.dart'; +import '../config/setting_keys.dart'; enum Themes { light, @@ -175,14 +176,12 @@ class ThemeSwitcherWidgetState extends State { BuildContext context; Future loadSelection(MatrixState matrix) async { - var item = await matrix.store.getItem('theme') ?? 'system'; + var item = await matrix.store.getItem(SettingKeys.theme) ?? 'system'; selectedTheme = Themes.values.firstWhere( (e) => e.toString() == 'Themes.' + item, orElse: () => Themes.system); - amoledEnabled = (await matrix.store.getItem('amoled_enabled') ?? 'false') - .toLowerCase() == - 'true'; + amoledEnabled = await matrix.store.getItemBool(SettingKeys.amoledEnabled); switchTheme(matrix, selectedTheme, amoledEnabled); return; @@ -229,11 +228,12 @@ class ThemeSwitcherWidgetState extends State { } Future saveThemeValue(MatrixState matrix, Themes value) async { - await matrix.store.setItem('theme', value.toString().split('.').last); + await matrix.store + .setItem(SettingKeys.theme, value.toString().split('.').last); } Future saveAmoledEnabledValue(MatrixState matrix, bool value) async { - await matrix.store.setItem('amoled_enabled', value.toString()); + await matrix.store.setItem(SettingKeys.amoledEnabled, value.toString()); } void setup() async { diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index a0c98cec..a2e4320f 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -8,4 +8,7 @@ abstract class AppConfig { 'https://gitlab.com/ChristianPauly/fluffychat-flutter/issues'; static const String sentryDsn = 'https://8591d0d863b646feb4f3dda7e5dcab38@o256755.ingest.sentry.io/5243143'; + static bool renderHtml = false; + static bool hideRedactedEvents = false; + static bool hideUnknownEvents = false; } diff --git a/lib/config/setting_keys.dart b/lib/config/setting_keys.dart new file mode 100644 index 00000000..08c8ecb2 --- /dev/null +++ b/lib/config/setting_keys.dart @@ -0,0 +1,13 @@ +abstract class SettingKeys { + static const String jitsiInstance = 'chat.fluffy.jitsi_instance'; + static const String wallpaper = 'chat.fluffy.wallpaper'; + static const String renderHtml = 'chat.fluffy.renderHtml'; + static const String hideRedactedEvents = 'chat.fluffy.hideRedactedEvents'; + static const String hideUnknownEvents = 'chat.fluffy.hideUnknownEvents'; + 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 databasePassword = 'database-password'; +} diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 94237671..46f8aa0d 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -681,6 +681,16 @@ "type": "text", "placeholders": {} }, + "hideRedactedEvents": "Hide redacted events", + "@hideRedactedEvents": { + "type": "text", + "placeholders": {} + }, + "hideUnknownEvents": "Hide unknown events", + "@hideUnknownEvents": { + "type": "text", + "placeholders": {} + }, "homeserverIsNotCompatible": "Homeserver is not compatible", "@homeserverIsNotCompatible": { "type": "text", diff --git a/lib/utils/famedlysdk_store.dart b/lib/utils/famedlysdk_store.dart index 34232e59..0ce242d8 100644 --- a/lib/utils/famedlysdk_store.dart +++ b/lib/utils/famedlysdk_store.dart @@ -6,6 +6,7 @@ import 'package:path_provider/path_provider.dart'; import 'dart:async'; import 'dart:core'; import './database/shared.dart'; +import '../config/setting_keys.dart'; import 'package:random_string/random_string.dart'; Future getDatabase(Client client) async { @@ -16,7 +17,7 @@ Future getDatabase(Client client) async { try { if (_db != null) return _db; final store = Store(); - var password = await store.getItem('database-password'); + var password = await store.getItem(SettingKeys.databasePassword); var newPassword = false; if (password == null || password.isEmpty) { newPassword = true; @@ -28,7 +29,7 @@ Future getDatabase(Client client) async { password: password, ); if (newPassword) { - await store.setItem('database-password', password); + await store.setItem(SettingKeys.databasePassword, password); } return _db; } finally { @@ -74,6 +75,15 @@ class Store { } } + Future getItemBool(String key, [bool defaultValue]) async { + final value = await getItem(key); + if (value == null) { + return defaultValue ?? false; + } + // we also check for '1' for legacy reasons, some booleans were stored that way + return value == '1' || value.toLowerCase() == 'true'; + } + Future setItem(String key, String value) async { if (!PlatformInfos.isMobile) { await _setupLocalStorage(); diff --git a/lib/utils/firebase_controller.dart b/lib/utils/firebase_controller.dart index 02c12467..859bbd38 100644 --- a/lib/utils/firebase_controller.dart +++ b/lib/utils/firebase_controller.dart @@ -15,6 +15,7 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:path_provider/path_provider.dart'; import '../components/matrix.dart'; +import '../config/setting_keys.dart'; import 'famedlysdk_store.dart'; import 'matrix_locals.dart'; @@ -42,8 +43,7 @@ abstract class FirebaseController { token = null; } if (token?.isEmpty ?? true) { - final storeItem = - await matrix.store.getItem('chat.fluffy.show_no_google'); + final storeItem = await matrix.store.getItem(SettingKeys.showNoGoogle); final configOptionMissing = storeItem == null || storeItem.isEmpty; if (configOptionMissing || (!configOptionMissing && storeItem == '1')) { BotToast.showText( @@ -51,7 +51,7 @@ abstract class FirebaseController { duration: Duration(seconds: 15), ); if (configOptionMissing) { - await matrix.store.setItem('chat.fluffy.show_no_google', '0'); + await matrix.store.setItem(SettingKeys.showNoGoogle, '0'); } } return; diff --git a/lib/utils/sentry_controller.dart b/lib/utils/sentry_controller.dart index 8cebc6c1..a152a4e4 100644 --- a/lib/utils/sentry_controller.dart +++ b/lib/utils/sentry_controller.dart @@ -7,6 +7,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:sentry/sentry.dart'; import 'famedlysdk_store.dart'; +import '../config/setting_keys.dart'; abstract class SentryController { static Future toggleSentryAction(BuildContext context) async { @@ -17,14 +18,14 @@ abstract class SentryController { cancelText: L10n.of(context).no, ); final storage = Store(); - await storage.setItem('sentry', enableSentry.toString()); + await storage.setItem(SettingKeys.sentry, enableSentry.toString()); BotToast.showText(text: L10n.of(context).changesHaveBeenSaved); return; } static Future getSentryStatus() async { final storage = Store(); - return await storage.getItem('sentry') == 'true'; + return await storage.getItemBool(SettingKeys.sentry); } static final sentry = SentryClient(dsn: AppConfig.sentryDsn); diff --git a/lib/views/chat.dart b/lib/views/chat.dart index 49aa2d1f..92d20aa9 100644 --- a/lib/views/chat.dart +++ b/lib/views/chat.dart @@ -34,6 +34,7 @@ import 'package:swipe_to_action/swipe_to_action.dart'; import '../components/dialogs/send_file_dialog.dart'; import '../components/input_bar.dart'; import '../utils/matrix_file_extension.dart'; +import '../config/app_config.dart'; import 'chat_details.dart'; import 'chat_list.dart'; @@ -411,9 +412,15 @@ class _ChatState extends State<_Chat> { List getFilteredEvents() => timeline.events .where((e) => - ![RelationshipTypes.Edit, RelationshipTypes.Reaction] + // always filter out edit and reaction relationships + !{RelationshipTypes.Edit, RelationshipTypes.Reaction} .contains(e.relationshipType) && - e.type != 'm.reaction') + // if a reaction has been redacted we also want it to appear in the timeline + e.type != EventTypes.Reaction && + // if we enabled to hide all redacted events, don't show those + (!AppConfig.hideRedactedEvents || !e.redacted) && + // if we enabled to hide all unknown events, don't show those + (!AppConfig.hideUnknownEvents || e.isEventTypeKnown)) .toList(); @override diff --git a/lib/views/settings.dart b/lib/views/settings.dart index bd4d4a54..ce62e6c2 100644 --- a/lib/views/settings.dart +++ b/lib/views/settings.dart @@ -21,6 +21,8 @@ import '../components/content_banner.dart'; import '../components/dialogs/simple_dialogs.dart'; import '../components/matrix.dart'; import '../utils/app_route.dart'; +import '../config/app_config.dart'; +import '../config/setting_keys.dart'; import 'app_info.dart'; import 'chat_list.dart'; import 'settings_emotes.dart'; @@ -115,7 +117,7 @@ class _SettingsState extends State { jitsi += '/'; } final matrix = Matrix.of(context); - await matrix.store.setItem('chat.fluffy.jitsi_instance', jitsi); + await matrix.store.setItem(SettingKeys.jitsiInstance, jitsi); matrix.jitsiInstance = jitsi; } @@ -179,13 +181,13 @@ class _SettingsState extends State { Matrix.of(context).wallpaper = File(wallpaper.path); await Matrix.of(context) .store - .setItem('chat.fluffy.wallpaper', wallpaper.path); + .setItem(SettingKeys.wallpaper, wallpaper.path); setState(() => null); } void deleteWallpaperAction(BuildContext context) async { Matrix.of(context).wallpaper = null; - await Matrix.of(context).store.deleteItem('chat.fluffy.wallpaper'); + await Matrix.of(context).store.deleteItem(SettingKeys.wallpaper); setState(() => null); } @@ -340,13 +342,39 @@ class _SettingsState extends State { ListTile( title: Text(L10n.of(context).renderRichContent), trailing: Switch( - value: Matrix.of(context).renderHtml, + value: AppConfig.renderHtml, activeColor: Theme.of(context).primaryColor, onChanged: (bool newValue) async { - Matrix.of(context).renderHtml = newValue; + AppConfig.renderHtml = newValue; await Matrix.of(context) .store - .setItem('chat.fluffy.renderHtml', newValue ? '1' : '0'); + .setItem(SettingKeys.renderHtml, newValue.toString()); + setState(() => null); + }, + ), + ), + ListTile( + title: Text(L10n.of(context).hideRedactedEvents), + trailing: Switch( + value: AppConfig.hideRedactedEvents, + activeColor: Theme.of(context).primaryColor, + onChanged: (bool newValue) async { + AppConfig.hideRedactedEvents = newValue; + await Matrix.of(context).store.setItem( + SettingKeys.hideRedactedEvents, newValue.toString()); + setState(() => null); + }, + ), + ), + ListTile( + title: Text(L10n.of(context).hideUnknownEvents), + trailing: Switch( + value: AppConfig.hideUnknownEvents, + activeColor: Theme.of(context).primaryColor, + onChanged: (bool newValue) async { + AppConfig.hideUnknownEvents = newValue; + await Matrix.of(context).store.setItem( + SettingKeys.hideUnknownEvents, newValue.toString()); setState(() => null); }, ), diff --git a/pubspec.lock b/pubspec.lock index 59f4c789..df5a2261 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -208,8 +208,8 @@ packages: dependency: "direct main" description: path: "." - ref: "15d817023d34f813e95eba6ca8c71c575b8c2457" - resolved-ref: "15d817023d34f813e95eba6ca8c71c575b8c2457" + ref: "66572bd03209c1c6488cde53a0c72c11faef341d" + resolved-ref: "66572bd03209c1c6488cde53a0c72c11faef341d" url: "https://gitlab.com/famedly/famedlysdk.git" source: git version: "0.0.1" diff --git a/pubspec.yaml b/pubspec.yaml index d14e03d0..9389847f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,7 +23,7 @@ dependencies: famedlysdk: git: url: https://gitlab.com/famedly/famedlysdk.git - ref: 15d817023d34f813e95eba6ca8c71c575b8c2457 + ref: 66572bd03209c1c6488cde53a0c72c11faef341d localstorage: ^3.0.3+6 file_picker_cross: 4.2.2