diff --git a/lib/components/settings_themes.dart b/lib/components/settings_themes.dart index 6837a43c..61a6b67d 100644 --- a/lib/components/settings_themes.dart +++ b/lib/components/settings_themes.dart @@ -64,20 +64,18 @@ class ThemesSettingsState extends State { }); }, ), - ListTile( + SwitchListTile( title: Text( L10n.of(context).useAmoledTheme, ), - trailing: Switch( - value: _amoledEnabled, - activeColor: Theme.of(context).primaryColor, - onChanged: (bool value) { - setState(() { - _amoledEnabled = value; - themeEngine.switchTheme(matrix, _selectedTheme, value); - }); - }, - ), + value: _amoledEnabled, + activeColor: Theme.of(context).primaryColor, + onChanged: (bool value) { + setState(() { + _amoledEnabled = value; + themeEngine.switchTheme(matrix, _selectedTheme, value); + }); + }, ), ], ); diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index b7a6c89f..f38bb03e 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -559,6 +559,51 @@ "type": "text", "placeholders": {} }, + "directChats": "Direct Chats", + "@directChats": { + "type": "text", + "placeholders": {} + }, + "containsDisplayName": "Contains display name", + "@containsDisplayName": { + "type": "text", + "placeholders": {} + }, + "containsUserName": "Contains user name", + "@containsUserName": { + "type": "text", + "placeholders": {} + }, + "inviteForMe": "Invite for me", + "@inviteForMe": { + "type": "text", + "placeholders": {} + }, + "memberChanges": "Member changes", + "@memberChanges": { + "type": "text", + "placeholders": {} + }, + "botMessages": "Bot messages", + "@botMessages": { + "type": "text", + "placeholders": {} + }, + "pushRules": "Push rules", + "@pushRules": { + "type": "text", + "placeholders": {} + }, + "notifications": "Notifications", + "@notifications": { + "type": "text", + "placeholders": {} + }, + "notificationsEnabledForThisAccount": "Notifications enabled for this account", + "@notificationsEnabledForThisAccount": { + "type": "text", + "placeholders": {} + }, "edit": "Edit", "@edit": { "type": "text", diff --git a/lib/views/settings.dart b/lib/views/settings.dart index 60615103..8b9000a8 100644 --- a/lib/views/settings.dart +++ b/lib/views/settings.dart @@ -1,12 +1,11 @@ -import 'dart:io'; - import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:fluffychat/views/settings_3pid.dart'; +import 'package:fluffychat/views/settings_notifications.dart'; +import 'package:fluffychat/views/settings_style.dart'; import 'package:flushbar/flushbar_helper.dart'; import 'package:famedlysdk/famedlysdk.dart'; import 'package:file_picker_cross/file_picker_cross.dart'; -import 'package:fluffychat/components/settings_themes.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/sentry_controller.dart'; @@ -202,22 +201,6 @@ class _SettingsState extends State { } } - void setWallpaperAction(BuildContext context) async { - final wallpaper = await ImagePicker().getImage(source: ImageSource.gallery); - if (wallpaper == null) return; - Matrix.of(context).wallpaper = File(wallpaper.path); - await Matrix.of(context) - .store - .setItem(SettingKeys.wallpaper, wallpaper.path); - setState(() => null); - } - - void deleteWallpaperAction(BuildContext context) async { - Matrix.of(context).wallpaper = null; - await Matrix.of(context).store.deleteItem(SettingKeys.wallpaper); - setState(() => null); - } - Future requestSSSSCache(BuildContext context) async { final handle = Matrix.of(context).client.encryption.ssss.open(); final input = await showTextInputDialog( @@ -323,48 +306,23 @@ class _SettingsState extends State { children: [ ListTile( title: Text( - L10n.of(context).changeTheme, + L10n.of(context).notifications, style: TextStyle( color: Theme.of(context).primaryColor, fontWeight: FontWeight.bold, ), ), ), - ThemesSettings(), - if (!kIsWeb && Matrix.of(context).store != null) - Divider(thickness: 1), - if (!kIsWeb && Matrix.of(context).store != null) - ListTile( - title: Text( - L10n.of(context).wallpaper, - style: TextStyle( - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.bold, - ), + ListTile( + trailing: Icon(Icons.notifications), + title: Text(L10n.of(context).notifications), + onTap: () async => await Navigator.of(context).push( + AppRoute.defaultRoute( + context, + SettingsNotificationsView(), ), ), - if (Matrix.of(context).wallpaper != null) - ListTile( - title: Image.file( - Matrix.of(context).wallpaper, - height: 38, - fit: BoxFit.cover, - ), - trailing: Icon( - Icons.delete_forever, - color: Colors.red, - ), - onTap: () => deleteWallpaperAction(context), - ), - if (!kIsWeb && Matrix.of(context).store != null) - Builder(builder: (context) { - return ListTile( - title: Text(L10n.of(context).changeWallpaper), - trailing: Icon(Icons.wallpaper), - onTap: () => setWallpaperAction(context), - ); - }), - Divider(thickness: 1), + ), ListTile( title: Text( L10n.of(context).chat, @@ -375,44 +333,45 @@ class _SettingsState extends State { ), ), ListTile( + title: Text(L10n.of(context).changeTheme), + onTap: () async => await Navigator.of(context).push( + AppRoute.defaultRoute( + context, + SettingsStyleView(), + ), + ), + trailing: Icon(Icons.wallpaper), + ), + SwitchListTile( title: Text(L10n.of(context).renderRichContent), - trailing: Switch( - value: AppConfig.renderHtml, - activeColor: Theme.of(context).primaryColor, - onChanged: (bool newValue) async { - AppConfig.renderHtml = newValue; - await Matrix.of(context) - .store - .setItem(SettingKeys.renderHtml, newValue.toString()); - setState(() => null); - }, - ), + value: AppConfig.renderHtml, + onChanged: (bool newValue) async { + AppConfig.renderHtml = newValue; + await Matrix.of(context) + .store + .setItem(SettingKeys.renderHtml, newValue.toString()); + setState(() => null); + }, ), - ListTile( + SwitchListTile( 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); - }, - ), + value: AppConfig.hideRedactedEvents, + onChanged: (bool newValue) async { + AppConfig.hideRedactedEvents = newValue; + await Matrix.of(context).store.setItem( + SettingKeys.hideRedactedEvents, newValue.toString()); + setState(() => null); + }, ), - ListTile( + SwitchListTile( 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); - }, - ), + value: AppConfig.hideUnknownEvents, + onChanged: (bool newValue) async { + AppConfig.hideUnknownEvents = newValue; + await Matrix.of(context).store.setItem( + SettingKeys.hideUnknownEvents, newValue.toString()); + setState(() => null); + }, ), ListTile( title: Text(L10n.of(context).emoteSettings), diff --git a/lib/views/settings_notifications.dart b/lib/views/settings_notifications.dart new file mode 100644 index 00000000..a2e87de9 --- /dev/null +++ b/lib/views/settings_notifications.dart @@ -0,0 +1,181 @@ +import 'dart:io'; + +import 'package:famedlysdk/famedlysdk.dart'; +import 'package:fluffychat/components/adaptive_page_layout.dart'; +import 'package:fluffychat/components/dialogs/simple_dialogs.dart'; +import 'package:fluffychat/utils/firebase_controller.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:open_noti_settings/open_noti_settings.dart'; + +import '../components/matrix.dart'; +import 'chat_list.dart'; + +class NotificationSettingsItem { + final PushRuleKind type; + final String key; + final String Function(BuildContext) title; + NotificationSettingsItem(this.type, this.key, this.title); +} + +class SettingsNotificationsView extends StatelessWidget { + @override + Widget build(BuildContext context) { + return AdaptivePageLayout( + primaryPage: FocusPage.SECOND, + firstScaffold: ChatList(), + secondScaffold: SettingsNotifications(), + ); + } +} + +class SettingsNotifications extends StatelessWidget { + static List items = [ + NotificationSettingsItem( + PushRuleKind.underride, + '.m.rule.room_one_to_one', + (c) => L10n.of(c).directChats, + ), + NotificationSettingsItem( + PushRuleKind.override, + '.m.rule.contains_display_name', + (c) => L10n.of(c).containsDisplayName, + ), + NotificationSettingsItem( + PushRuleKind.content, + '.m.rule.contains_user_name', + (c) => L10n.of(c).containsUserName, + ), + NotificationSettingsItem( + PushRuleKind.override, + '.m.rule.invite_for_me', + (c) => L10n.of(c).inviteForMe, + ), + NotificationSettingsItem( + PushRuleKind.override, + '.m.rule.member_event', + (c) => L10n.of(c).memberChanges, + ), + NotificationSettingsItem( + PushRuleKind.override, + '.m.rule.suppress_notices', + (c) => L10n.of(c).botMessages, + ), + ]; + void _openAndroidNotificationSettingsAction() async { + await NotificationSetting.configureChannel( + NotificationDetails( + android: AndroidNotificationDetails( + FirebaseController.CHANNEL_ID, + FirebaseController.CHANNEL_NAME, + FirebaseController.CHANNEL_DESCRIPTION, + ), + ), + ); + return NotificationSetting.open(); + } + + bool _getNotificationSetting( + BuildContext context, NotificationSettingsItem item) { + final pushRules = Matrix.of(context).client.globalPushRules; + switch (item.type) { + case PushRuleKind.content: + return pushRules.content + ?.singleWhere((r) => r.ruleId == item.key, orElse: () => null) + ?.enabled; + case PushRuleKind.override: + return pushRules.override + ?.singleWhere((r) => r.ruleId == item.key, orElse: () => null) + ?.enabled; + case PushRuleKind.room: + return pushRules.room + ?.singleWhere((r) => r.ruleId == item.key, orElse: () => null) + ?.enabled; + case PushRuleKind.sender: + return pushRules.sender + ?.singleWhere((r) => r.ruleId == item.key, orElse: () => null) + ?.enabled; + case PushRuleKind.underride: + return pushRules.underride + ?.singleWhere((r) => r.ruleId == item.key, orElse: () => null) + ?.enabled; + } + return false; + } + + void _setNotificationSetting( + BuildContext context, NotificationSettingsItem item, bool enabled) { + SimpleDialogs(context).tryRequestWithLoadingDialog( + Matrix.of(context).client.enablePushRule( + 'global', + item.type, + item.key, + enabled, + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(L10n.of(context).notifications), + ), + body: StreamBuilder( + stream: Matrix.of(context) + .client + .onAccountData + .stream + .where((event) => event.type == 'm.push_rules'), + builder: (BuildContext context, _) { + return ListView( + children: [ + SwitchListTile( + value: !Matrix.of(context).client.allPushNotificationsMuted, + title: + Text(L10n.of(context).notificationsEnabledForThisAccount), + onChanged: (_) => + SimpleDialogs(context).tryRequestWithLoadingDialog( + Matrix.of(context).client.setMuteAllPushNotifications( + !Matrix.of(context).client.allPushNotificationsMuted, + ), + ), + ), + if (!Matrix.of(context).client.allPushNotificationsMuted) ...{ + if (!kIsWeb && Platform.isAndroid) + ListTile( + title: Text('Ton, Vibration, LED-Farbe'), + trailing: CircleAvatar( + backgroundColor: + Theme.of(context).scaffoldBackgroundColor, + foregroundColor: Colors.grey, + child: Icon(Icons.edit_outlined), + ), + onTap: () => _openAndroidNotificationSettingsAction(), + ), + Divider(thickness: 1), + ListTile( + title: Text( + L10n.of(context).pushRules, + style: TextStyle( + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.bold, + ), + ), + ), + for (var item in items) + SwitchListTile( + value: _getNotificationSetting(context, item) ?? true, + title: Text(item.title(context)), + onChanged: (bool enabled) => + _setNotificationSetting(context, item, enabled), + ), + } + ], + ); + }), + ); + } +} diff --git a/lib/views/settings_style.dart b/lib/views/settings_style.dart new file mode 100644 index 00000000..74c6d4ad --- /dev/null +++ b/lib/views/settings_style.dart @@ -0,0 +1,89 @@ +import 'dart:io'; + +import 'package:fluffychat/components/adaptive_page_layout.dart'; +import 'package:fluffychat/components/settings_themes.dart'; +import 'package:fluffychat/config/setting_keys.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:image_picker/image_picker.dart'; + +import '../components/matrix.dart'; +import 'chat_list.dart'; + +class SettingsStyleView extends StatelessWidget { + @override + Widget build(BuildContext context) { + return AdaptivePageLayout( + primaryPage: FocusPage.SECOND, + firstScaffold: ChatList(), + secondScaffold: SettingsStyle(), + ); + } +} + +class SettingsStyle extends StatefulWidget { + @override + _SettingsStyleState createState() => _SettingsStyleState(); +} + +class _SettingsStyleState extends State { + void setWallpaperAction(BuildContext context) async { + final wallpaper = await ImagePicker().getImage(source: ImageSource.gallery); + if (wallpaper == null) return; + Matrix.of(context).wallpaper = File(wallpaper.path); + await Matrix.of(context) + .store + .setItem(SettingKeys.wallpaper, wallpaper.path); + setState(() => null); + } + + void deleteWallpaperAction(BuildContext context) async { + Matrix.of(context).wallpaper = null; + await Matrix.of(context).store.deleteItem(SettingKeys.wallpaper); + setState(() => null); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(L10n.of(context).changeTheme), + ), + body: ListView( + children: [ + ThemesSettings(), + Divider(thickness: 1), + ListTile( + title: Text( + L10n.of(context).wallpaper, + style: TextStyle( + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.bold, + ), + ), + ), + if (Matrix.of(context).wallpaper != null) + ListTile( + title: Image.file( + Matrix.of(context).wallpaper, + height: 38, + fit: BoxFit.cover, + ), + trailing: Icon( + Icons.delete_forever, + color: Colors.red, + ), + onTap: () => deleteWallpaperAction(context), + ), + Builder(builder: (context) { + return ListTile( + title: Text(L10n.of(context).changeWallpaper), + trailing: Icon(Icons.wallpaper), + onTap: () => setWallpaperAction(context), + ); + }), + ], + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 0bee27f1..6ca3c0d6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -42,7 +42,7 @@ packages: name: ansicolor url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.1.1" archive: dependency: transitive description: @@ -70,7 +70,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0-nullsafety.3" + version: "2.5.0-nullsafety.1" base58check: dependency: transitive description: @@ -84,7 +84,7 @@ packages: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.3" + version: "2.1.0-nullsafety.1" cached_network_image: dependency: "direct main" description: @@ -105,14 +105,14 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.5" + version: "1.1.0-nullsafety.3" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.3" + version: "1.2.0-nullsafety.1" circular_check_box: dependency: "direct main" description: @@ -133,14 +133,14 @@ packages: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.3" + version: "1.1.0-nullsafety.1" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0-nullsafety.5" + version: "1.15.0-nullsafety.3" convert: dependency: transitive description: @@ -203,13 +203,13 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.3" + version: "1.2.0-nullsafety.1" famedlysdk: dependency: "direct main" description: path: "." - ref: "01ce832aaa738d4e4432e1f0912b0427ce8d134c" - resolved-ref: "01ce832aaa738d4e4432e1f0912b0427ce8d134c" + ref: "20ae1ae20e51840643988768620cc9900f83231c" + resolved-ref: "20ae1ae20e51840643988768620cc9900f83231c" url: "https://gitlab.com/famedly/famedlysdk.git" source: git version: "0.0.1" @@ -343,7 +343,7 @@ packages: name: flutter_local_notifications url: "https://pub.dartlang.org" source: hosted - version: "3.0.1+5" + version: "3.0.1+6" flutter_local_notifications_platform_interface: dependency: transitive description: @@ -533,7 +533,7 @@ packages: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.3-nullsafety.3" + version: "0.6.3-nullsafety.2" localstorage: dependency: "direct main" description: @@ -561,7 +561,7 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10-nullsafety.3" + version: "0.12.10-nullsafety.1" matrix_file_e2ee: dependency: transitive description: @@ -582,7 +582,7 @@ packages: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.6" + version: "1.3.0-nullsafety.3" mime: dependency: transitive description: @@ -662,6 +662,15 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.3" + open_noti_settings: + dependency: "direct main" + description: + path: "." + ref: master + resolved-ref: ff18b3917672ff64b3882a184df96d008f87bbcd + url: "https://github.com/ChristianPauly/flutter_open_notification_settings.git" + source: git + version: "0.0.2+5" package_config: dependency: transitive description: @@ -689,7 +698,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.3" + version: "1.8.0-nullsafety.1" path_drawing: dependency: transitive description: @@ -925,7 +934,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.4" + version: "1.8.0-nullsafety.2" sqflite: dependency: "direct main" description: @@ -960,21 +969,21 @@ packages: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.10.0-nullsafety.6" + version: "1.10.0-nullsafety.1" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.3" + version: "2.1.0-nullsafety.1" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.3" + version: "1.1.0-nullsafety.1" swipe_to_action: dependency: "direct main" description: @@ -995,28 +1004,28 @@ packages: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.3" + version: "1.2.0-nullsafety.1" test: dependency: transitive description: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.16.0-nullsafety.12" + version: "1.16.0-nullsafety.5" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19-nullsafety.6" + version: "0.2.19-nullsafety.2" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.3.12-nullsafety.11" + version: "0.3.12-nullsafety.5" timezone: dependency: transitive description: @@ -1037,7 +1046,7 @@ packages: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.5" + version: "1.3.0-nullsafety.3" universal_html: dependency: "direct main" description: @@ -1121,7 +1130,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.5" + version: "2.1.0-nullsafety.3" vm_service: dependency: transitive description: @@ -1193,5 +1202,5 @@ packages: source: hosted version: "0.1.2" sdks: - dart: ">=2.12.0-0.0 <=2.12.0-29.10.beta" + dart: ">=2.10.2 <2.11.0" flutter: ">=1.22.2 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 8200933f..5eaeda20 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: famedlysdk: git: url: https://gitlab.com/famedly/famedlysdk.git - ref: 01ce832aaa738d4e4432e1f0912b0427ce8d134c + ref: 20ae1ae20e51840643988768620cc9900f83231c localstorage: ^3.0.3+6 file_picker_cross: 4.2.2 @@ -63,6 +63,10 @@ dependencies: swipe_to_action: ^0.1.0 flutter_svg: 0.19.1 # Because fluffychat depends on flutter_svg >=0.19.2 which requires Flutter SDK version >=1.24.0-6.0.pre <2.0.0, version solving failed. flutter_cache_manager: ^2.0.0 + open_noti_settings: + git: + url: https://github.com/ChristianPauly/flutter_open_notification_settings.git + ref: master dev_dependencies: flutter_test: