✨ Chat enter to send
This commit is contained in:
		| @@ -271,5 +271,12 @@ | ||||
|   "unreadMessages": { | ||||
|     "one": "{} unread message", | ||||
|     "other": "{} unread messages" | ||||
|   } | ||||
|   }, | ||||
|   "settingsRealmCompactView": "Compact Realm View", | ||||
|   "settingsMixedFeed": "Mixed Feed", | ||||
|   "settingsAutoTranslate": "Auto Translate", | ||||
|   "settingsHideBottomNav": "Hide Bottom Navigation", | ||||
|   "settingsSoundEffects": "Sound Effects", | ||||
|   "settingsAprilFoolFeatures": "April Fool Features", | ||||
|   "settingsEnterToSend": "Enter to Send" | ||||
| } | ||||
|   | ||||
| @@ -6,7 +6,7 @@ part of 'chat_summary.dart'; | ||||
| // RiverpodGenerator | ||||
| // ************************************************************************** | ||||
|  | ||||
| String _$chatSummaryHash() => r'fa48d381f489f90055fb728f7e0fda6f8ef49d15'; | ||||
| String _$chatSummaryHash() => r'19aad48b5fabb33a76b742400d3b738ceb81c40c'; | ||||
|  | ||||
| /// See also [ChatSummary]. | ||||
| @ProviderFor(ChatSummary) | ||||
|   | ||||
| @@ -1,7 +1,10 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||||
| import 'package:shared_preferences/shared_preferences.dart'; | ||||
|  | ||||
| part 'config.freezed.dart'; | ||||
|  | ||||
| const kTokenPairStoreKey = 'dyn_user_tk'; | ||||
|  | ||||
| const kNetworkServerDefault = 'https://nt.solian.app'; | ||||
| @@ -21,6 +24,7 @@ const kAppHideBottomNav = 'app_hide_bottom_nav'; | ||||
| const kAppSoundEffects = 'app_sound_effects'; | ||||
| const kAppAprilFoolFeatures = 'app_april_fool_features'; | ||||
| const kAppWindowSize = 'app_window_size'; | ||||
| const kAppEnterToSend = 'app_enter_to_send'; | ||||
|  | ||||
| const Map<String, FilterQuality> kImageQualityLevel = { | ||||
|   'settingsImageQualityLowest': FilterQuality.none, | ||||
| @@ -46,40 +50,17 @@ final serverUrlProvider = Provider<String>((ref) { | ||||
|   return prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault; | ||||
| }); | ||||
|  | ||||
| class AppSettings { | ||||
|   final bool realmCompactView; | ||||
|   final bool mixedFeed; | ||||
|   final bool autoTranslate; | ||||
|   final bool hideBottomNav; | ||||
|   final bool soundEffects; | ||||
|   final bool aprilFoolFeatures; | ||||
|  | ||||
|   AppSettings({ | ||||
|     required this.realmCompactView, | ||||
|     required this.mixedFeed, | ||||
|     required this.autoTranslate, | ||||
|     required this.hideBottomNav, | ||||
|     required this.soundEffects, | ||||
|     required this.aprilFoolFeatures, | ||||
|   }); | ||||
|  | ||||
|   AppSettings copyWith({ | ||||
|     bool? realmCompactView, | ||||
|     bool? mixedFeed, | ||||
|     bool? autoTranslate, | ||||
|     bool? hideBottomNav, | ||||
|     bool? soundEffects, | ||||
|     bool? aprilFoolFeatures, | ||||
|   }) { | ||||
|     return AppSettings( | ||||
|       realmCompactView: realmCompactView ?? this.realmCompactView, | ||||
|       mixedFeed: mixedFeed ?? this.mixedFeed, | ||||
|       autoTranslate: autoTranslate ?? this.autoTranslate, | ||||
|       hideBottomNav: hideBottomNav ?? this.hideBottomNav, | ||||
|       soundEffects: soundEffects ?? this.soundEffects, | ||||
|       aprilFoolFeatures: aprilFoolFeatures ?? this.aprilFoolFeatures, | ||||
|     ); | ||||
|   } | ||||
| @freezed | ||||
| abstract class AppSettings with _$AppSettings { | ||||
|   const factory AppSettings({ | ||||
|     required bool realmCompactView, | ||||
|     required bool mixedFeed, | ||||
|     required bool autoTranslate, | ||||
|     required bool hideBottomNav, | ||||
|     required bool soundEffects, | ||||
|     required bool aprilFoolFeatures, | ||||
|     required bool enterToSend, | ||||
|   }) = _AppSettings; | ||||
| } | ||||
|  | ||||
| class AppSettingsNotifier extends StateNotifier<AppSettings> { | ||||
| @@ -94,6 +75,7 @@ class AppSettingsNotifier extends StateNotifier<AppSettings> { | ||||
|           hideBottomNav: prefs.getBool(kAppHideBottomNav) ?? false, | ||||
|           soundEffects: prefs.getBool(kAppSoundEffects) ?? true, | ||||
|           aprilFoolFeatures: prefs.getBool(kAppAprilFoolFeatures) ?? true, | ||||
|           enterToSend: prefs.getBool(kAppEnterToSend) ?? true, | ||||
|         ), | ||||
|       ); | ||||
|  | ||||
| @@ -126,6 +108,11 @@ class AppSettingsNotifier extends StateNotifier<AppSettings> { | ||||
|     prefs.setBool(kAppAprilFoolFeatures, value); | ||||
|     state = state.copyWith(aprilFoolFeatures: value); | ||||
|   } | ||||
|  | ||||
|   void setEnterToSend(bool value) { | ||||
|     prefs.setBool(kAppEnterToSend, value); | ||||
|     state = state.copyWith(enterToSend: value); | ||||
|   } | ||||
| } | ||||
|  | ||||
| final appSettingsProvider = | ||||
|   | ||||
							
								
								
									
										160
									
								
								lib/pods/config.freezed.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								lib/pods/config.freezed.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,160 @@ | ||||
| // dart format width=80 | ||||
| // coverage:ignore-file | ||||
| // GENERATED CODE - DO NOT MODIFY BY HAND | ||||
| // ignore_for_file: type=lint | ||||
| // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark | ||||
|  | ||||
| part of 'config.dart'; | ||||
|  | ||||
| // ************************************************************************** | ||||
| // FreezedGenerator | ||||
| // ************************************************************************** | ||||
|  | ||||
| // dart format off | ||||
| T _$identity<T>(T value) => value; | ||||
| /// @nodoc | ||||
| mixin _$AppSettings { | ||||
|  | ||||
|  bool get realmCompactView; bool get mixedFeed; bool get autoTranslate; bool get hideBottomNav; bool get soundEffects; bool get aprilFoolFeatures; bool get enterToSend; | ||||
| /// Create a copy of AppSettings | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @pragma('vm:prefer-inline') | ||||
| $AppSettingsCopyWith<AppSettings> get copyWith => _$AppSettingsCopyWithImpl<AppSettings>(this as AppSettings, _$identity); | ||||
|  | ||||
|  | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is AppSettings&&(identical(other.realmCompactView, realmCompactView) || other.realmCompactView == realmCompactView)&&(identical(other.mixedFeed, mixedFeed) || other.mixedFeed == mixedFeed)&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.hideBottomNav, hideBottomNav) || other.hideBottomNav == hideBottomNav)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)); | ||||
| } | ||||
|  | ||||
|  | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,realmCompactView,mixedFeed,autoTranslate,hideBottomNav,soundEffects,aprilFoolFeatures,enterToSend); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'AppSettings(realmCompactView: $realmCompactView, mixedFeed: $mixedFeed, autoTranslate: $autoTranslate, hideBottomNav: $hideBottomNav, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract mixin class $AppSettingsCopyWith<$Res>  { | ||||
|   factory $AppSettingsCopyWith(AppSettings value, $Res Function(AppSettings) _then) = _$AppSettingsCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  bool realmCompactView, bool mixedFeed, bool autoTranslate, bool hideBottomNav, bool soundEffects, bool aprilFoolFeatures, bool enterToSend | ||||
| }); | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| class _$AppSettingsCopyWithImpl<$Res> | ||||
|     implements $AppSettingsCopyWith<$Res> { | ||||
|   _$AppSettingsCopyWithImpl(this._self, this._then); | ||||
|  | ||||
|   final AppSettings _self; | ||||
|   final $Res Function(AppSettings) _then; | ||||
|  | ||||
| /// Create a copy of AppSettings | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? realmCompactView = null,Object? mixedFeed = null,Object? autoTranslate = null,Object? hideBottomNav = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,}) { | ||||
|   return _then(_self.copyWith( | ||||
| realmCompactView: null == realmCompactView ? _self.realmCompactView : realmCompactView // ignore: cast_nullable_to_non_nullable | ||||
| as bool,mixedFeed: null == mixedFeed ? _self.mixedFeed : mixedFeed // ignore: cast_nullable_to_non_nullable | ||||
| as bool,autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable | ||||
| as bool,hideBottomNav: null == hideBottomNav ? _self.hideBottomNav : hideBottomNav // ignore: cast_nullable_to_non_nullable | ||||
| as bool,soundEffects: null == soundEffects ? _self.soundEffects : soundEffects // ignore: cast_nullable_to_non_nullable | ||||
| as bool,aprilFoolFeatures: null == aprilFoolFeatures ? _self.aprilFoolFeatures : aprilFoolFeatures // ignore: cast_nullable_to_non_nullable | ||||
| as bool,enterToSend: null == enterToSend ? _self.enterToSend : enterToSend // ignore: cast_nullable_to_non_nullable | ||||
| as bool, | ||||
|   )); | ||||
| } | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| /// @nodoc | ||||
|  | ||||
|  | ||||
| class _AppSettings implements AppSettings { | ||||
|   const _AppSettings({required this.realmCompactView, required this.mixedFeed, required this.autoTranslate, required this.hideBottomNav, required this.soundEffects, required this.aprilFoolFeatures, required this.enterToSend}); | ||||
|    | ||||
|  | ||||
| @override final  bool realmCompactView; | ||||
| @override final  bool mixedFeed; | ||||
| @override final  bool autoTranslate; | ||||
| @override final  bool hideBottomNav; | ||||
| @override final  bool soundEffects; | ||||
| @override final  bool aprilFoolFeatures; | ||||
| @override final  bool enterToSend; | ||||
|  | ||||
| /// Create a copy of AppSettings | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @pragma('vm:prefer-inline') | ||||
| _$AppSettingsCopyWith<_AppSettings> get copyWith => __$AppSettingsCopyWithImpl<_AppSettings>(this, _$identity); | ||||
|  | ||||
|  | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettings&&(identical(other.realmCompactView, realmCompactView) || other.realmCompactView == realmCompactView)&&(identical(other.mixedFeed, mixedFeed) || other.mixedFeed == mixedFeed)&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.hideBottomNav, hideBottomNav) || other.hideBottomNav == hideBottomNav)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)); | ||||
| } | ||||
|  | ||||
|  | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,realmCompactView,mixedFeed,autoTranslate,hideBottomNav,soundEffects,aprilFoolFeatures,enterToSend); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'AppSettings(realmCompactView: $realmCompactView, mixedFeed: $mixedFeed, autoTranslate: $autoTranslate, hideBottomNav: $hideBottomNav, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| /// @nodoc | ||||
| abstract mixin class _$AppSettingsCopyWith<$Res> implements $AppSettingsCopyWith<$Res> { | ||||
|   factory _$AppSettingsCopyWith(_AppSettings value, $Res Function(_AppSettings) _then) = __$AppSettingsCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  bool realmCompactView, bool mixedFeed, bool autoTranslate, bool hideBottomNav, bool soundEffects, bool aprilFoolFeatures, bool enterToSend | ||||
| }); | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| } | ||||
| /// @nodoc | ||||
| class __$AppSettingsCopyWithImpl<$Res> | ||||
|     implements _$AppSettingsCopyWith<$Res> { | ||||
|   __$AppSettingsCopyWithImpl(this._self, this._then); | ||||
|  | ||||
|   final _AppSettings _self; | ||||
|   final $Res Function(_AppSettings) _then; | ||||
|  | ||||
| /// Create a copy of AppSettings | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? realmCompactView = null,Object? mixedFeed = null,Object? autoTranslate = null,Object? hideBottomNav = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,}) { | ||||
|   return _then(_AppSettings( | ||||
| realmCompactView: null == realmCompactView ? _self.realmCompactView : realmCompactView // ignore: cast_nullable_to_non_nullable | ||||
| as bool,mixedFeed: null == mixedFeed ? _self.mixedFeed : mixedFeed // ignore: cast_nullable_to_non_nullable | ||||
| as bool,autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable | ||||
| as bool,hideBottomNav: null == hideBottomNav ? _self.hideBottomNav : hideBottomNav // ignore: cast_nullable_to_non_nullable | ||||
| as bool,soundEffects: null == soundEffects ? _self.soundEffects : soundEffects // ignore: cast_nullable_to_non_nullable | ||||
| as bool,aprilFoolFeatures: null == aprilFoolFeatures ? _self.aprilFoolFeatures : aprilFoolFeatures // ignore: cast_nullable_to_non_nullable | ||||
| as bool,enterToSend: null == enterToSend ? _self.enterToSend : enterToSend // ignore: cast_nullable_to_non_nullable | ||||
| as bool, | ||||
|   )); | ||||
| } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| // dart format on | ||||
| @@ -93,9 +93,12 @@ class ChatRoomListTile extends HookConsumerWidget { | ||||
|                       style: Theme.of(context).textTheme.bodySmall, | ||||
|                     ), | ||||
|                   ), | ||||
|                   Text( | ||||
|                     RelativeTime(context).format(data.lastMessage.createdAt), | ||||
|                     style: Theme.of(context).textTheme.bodySmall, | ||||
|                   Align( | ||||
|                     alignment: Alignment.centerRight, | ||||
|                     child: Text( | ||||
|                       RelativeTime(context).format(data.lastMessage.createdAt), | ||||
|                       style: Theme.of(context).textTheme.bodySmall, | ||||
|                     ), | ||||
|                   ), | ||||
|                 ], | ||||
|               ), | ||||
| @@ -117,41 +120,31 @@ class ChatRoomListTile extends HookConsumerWidget { | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     Widget buildTrailing() { | ||||
|       if (trailing != null) return trailing!; | ||||
|  | ||||
|       return summary.when( | ||||
|         data: (data) { | ||||
|           if (data == null || data.unreadCount == 0) { | ||||
|             return const SizedBox.shrink(); | ||||
|           } | ||||
|  | ||||
|           return Badge(label: Text(data.unreadCount.toString())); | ||||
|         }, | ||||
|         loading: () => const SizedBox.shrink(), | ||||
|         error: (_, __) => const SizedBox.shrink(), | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return ListTile( | ||||
|       leading: | ||||
|           (isDirect && room.pictureId == null) | ||||
|               ? SplitAvatarWidget( | ||||
|                 filesId: | ||||
|                     room.members! | ||||
|                         .map((e) => e.account.profile.pictureId) | ||||
|                         .toList(), | ||||
|               ) | ||||
|               : room.pictureId == null | ||||
|               ? CircleAvatar(child: Text(room.name![0].toUpperCase())) | ||||
|               : ProfilePictureWidget(fileId: room.pictureId), | ||||
|       leading: Badge( | ||||
|         isLabelVisible: summary.when( | ||||
|           data: (data) => (data?.unreadCount ?? 0) > 0, | ||||
|           loading: () => false, | ||||
|           error: (_, __) => false, | ||||
|         ), | ||||
|         child: | ||||
|             (isDirect && room.pictureId == null) | ||||
|                 ? SplitAvatarWidget( | ||||
|                   filesId: | ||||
|                       room.members! | ||||
|                           .map((e) => e.account.profile.pictureId) | ||||
|                           .toList(), | ||||
|                 ) | ||||
|                 : room.pictureId == null | ||||
|                 ? CircleAvatar(child: Text(room.name![0].toUpperCase())) | ||||
|                 : ProfilePictureWidget(fileId: room.pictureId), | ||||
|       ), | ||||
|       title: Text( | ||||
|         (isDirect && room.name == null) | ||||
|             ? room.members!.map((e) => e.account.nick).join(', ') | ||||
|             : room.name ?? '', | ||||
|       ), | ||||
|       subtitle: buildSubtitle(), | ||||
|       trailing: buildTrailing(), | ||||
|       onTap: () async { | ||||
|         // Clear unread count if there are unread messages | ||||
|         final summary = await ref.read(chatSummaryProvider.future); | ||||
|   | ||||
| @@ -3,6 +3,7 @@ import 'dart:convert'; | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:gap/gap.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| @@ -676,7 +677,7 @@ class ChatRoomScreen extends HookConsumerWidget { | ||||
|   } | ||||
| } | ||||
|  | ||||
| class _ChatInput extends StatelessWidget { | ||||
| class _ChatInput extends ConsumerWidget { | ||||
|   final TextEditingController messageController; | ||||
|   final SnChatRoom chatRoom; | ||||
|   final VoidCallback onSend; | ||||
| @@ -705,8 +706,26 @@ class _ChatInput extends StatelessWidget { | ||||
|     required this.onMoveAttachment, | ||||
|   }); | ||||
|  | ||||
|   void _handleKeyPress(BuildContext context, WidgetRef ref, RawKeyEvent event) { | ||||
|     if (event is! RawKeyDownEvent) return; | ||||
|  | ||||
|     final enterToSend = ref.read(appSettingsProvider).enterToSend; | ||||
|     final isEnter = event.logicalKey == LogicalKeyboardKey.enter; | ||||
|     final isModifierPressed = event.isMetaPressed || event.isControlPressed; | ||||
|  | ||||
|     if (isEnter) { | ||||
|       if (enterToSend && !isModifierPressed) { | ||||
|         onSend(); | ||||
|       } else if (!enterToSend && isModifierPressed) { | ||||
|         onSend(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final enterToSend = ref.watch(appSettingsProvider).enterToSend; | ||||
|  | ||||
|     return Material( | ||||
|       elevation: 8, | ||||
|       color: Theme.of(context).colorScheme.surface, | ||||
| @@ -806,30 +825,42 @@ class _ChatInput extends StatelessWidget { | ||||
|                       ], | ||||
|                 ), | ||||
|                 Expanded( | ||||
|                   child: TextField( | ||||
|                     controller: messageController, | ||||
|                     decoration: InputDecoration( | ||||
|                       hintText: | ||||
|                           (chatRoom.type == 1 && chatRoom.name == null) | ||||
|                               ? 'chatDirectMessageHint'.tr( | ||||
|                                 args: [ | ||||
|                                   chatRoom.members! | ||||
|                                       .map((e) => e.account.nick) | ||||
|                                       .join(', '), | ||||
|                                 ], | ||||
|                               ) | ||||
|                               : 'chatMessageHint'.tr(args: [chatRoom.name!]), | ||||
|                       border: InputBorder.none, | ||||
|                       isDense: true, | ||||
|                       contentPadding: const EdgeInsets.symmetric( | ||||
|                         horizontal: 12, | ||||
|                         vertical: 4, | ||||
|                   child: RawKeyboardListener( | ||||
|                     focusNode: FocusNode(), | ||||
|                     onKey: (event) => _handleKeyPress(context, ref, event), | ||||
|                     child: TextField( | ||||
|                       controller: messageController, | ||||
|                       inputFormatters: [ | ||||
|                         if (enterToSend) | ||||
|                           TextInputFormatter.withFunction((oldValue, newValue) { | ||||
|                             if (newValue.text.endsWith('\n')) { | ||||
|                               return oldValue; | ||||
|                             } | ||||
|                             return newValue; | ||||
|                           }), | ||||
|                       ], | ||||
|                       decoration: InputDecoration( | ||||
|                         hintText: | ||||
|                             (chatRoom.type == 1 && chatRoom.name == null) | ||||
|                                 ? 'chatDirectMessageHint'.tr( | ||||
|                                   args: [ | ||||
|                                     chatRoom.members! | ||||
|                                         .map((e) => e.account.nick) | ||||
|                                         .join(', '), | ||||
|                                   ], | ||||
|                                 ) | ||||
|                                 : 'chatMessageHint'.tr(args: [chatRoom.name!]), | ||||
|                         border: InputBorder.none, | ||||
|                         isDense: true, | ||||
|                         contentPadding: const EdgeInsets.symmetric( | ||||
|                           horizontal: 12, | ||||
|                           vertical: 4, | ||||
|                         ), | ||||
|                       ), | ||||
|                       maxLines: null, | ||||
|                       onTapOutside: | ||||
|                           (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||
|                     ), | ||||
|                     maxLines: null, | ||||
|                     onTapOutside: | ||||
|                         (_) => FocusManager.instance.primaryFocus?.unfocus(), | ||||
|                     onSubmitted: (_) => onSend(), | ||||
|                   ), | ||||
|                 ), | ||||
|                 IconButton( | ||||
|   | ||||
| @@ -26,6 +26,7 @@ class SettingsScreen extends HookConsumerWidget { | ||||
|     final serverUrl = ref.watch(serverUrlProvider); | ||||
|     final prefs = ref.watch(sharedPreferencesProvider); | ||||
|     final controller = TextEditingController(text: serverUrl); | ||||
|     final settings = ref.watch(appSettingsProvider); | ||||
|  | ||||
|     final docBasepath = useState<String?>(null); | ||||
|  | ||||
| @@ -174,6 +175,99 @@ class SettingsScreen extends HookConsumerWidget { | ||||
|                   ); | ||||
|                 }, | ||||
|               ), | ||||
|             const Divider(), | ||||
|             ListTile( | ||||
|               minLeadingWidth: 48, | ||||
|               title: Text('settingsRealmCompactView').tr(), | ||||
|               contentPadding: const EdgeInsets.only(left: 24, right: 17), | ||||
|               leading: const Icon(Symbols.view_compact), | ||||
|               trailing: Switch( | ||||
|                 value: settings.realmCompactView, | ||||
|                 onChanged: (value) { | ||||
|                   ref | ||||
|                       .read(appSettingsProvider.notifier) | ||||
|                       .setRealmCompactView(value); | ||||
|                 }, | ||||
|               ), | ||||
|             ), | ||||
|             ListTile( | ||||
|               minLeadingWidth: 48, | ||||
|               title: Text('settingsMixedFeed').tr(), | ||||
|               contentPadding: const EdgeInsets.only(left: 24, right: 17), | ||||
|               leading: const Icon(Symbols.merge), | ||||
|               trailing: Switch( | ||||
|                 value: settings.mixedFeed, | ||||
|                 onChanged: (value) { | ||||
|                   ref.read(appSettingsProvider.notifier).setMixedFeed(value); | ||||
|                 }, | ||||
|               ), | ||||
|             ), | ||||
|             ListTile( | ||||
|               minLeadingWidth: 48, | ||||
|               title: Text('settingsAutoTranslate').tr(), | ||||
|               contentPadding: const EdgeInsets.only(left: 24, right: 17), | ||||
|               leading: const Icon(Symbols.translate), | ||||
|               trailing: Switch( | ||||
|                 value: settings.autoTranslate, | ||||
|                 onChanged: (value) { | ||||
|                   ref | ||||
|                       .read(appSettingsProvider.notifier) | ||||
|                       .setAutoTranslate(value); | ||||
|                 }, | ||||
|               ), | ||||
|             ), | ||||
|             ListTile( | ||||
|               minLeadingWidth: 48, | ||||
|               title: Text('settingsHideBottomNav').tr(), | ||||
|               contentPadding: const EdgeInsets.only(left: 24, right: 17), | ||||
|               leading: const Icon(Symbols.navigation), | ||||
|               trailing: Switch( | ||||
|                 value: settings.hideBottomNav, | ||||
|                 onChanged: (value) { | ||||
|                   ref | ||||
|                       .read(appSettingsProvider.notifier) | ||||
|                       .setHideBottomNav(value); | ||||
|                 }, | ||||
|               ), | ||||
|             ), | ||||
|             ListTile( | ||||
|               minLeadingWidth: 48, | ||||
|               title: Text('settingsSoundEffects').tr(), | ||||
|               contentPadding: const EdgeInsets.only(left: 24, right: 17), | ||||
|               leading: const Icon(Symbols.volume_up), | ||||
|               trailing: Switch( | ||||
|                 value: settings.soundEffects, | ||||
|                 onChanged: (value) { | ||||
|                   ref.read(appSettingsProvider.notifier).setSoundEffects(value); | ||||
|                 }, | ||||
|               ), | ||||
|             ), | ||||
|             ListTile( | ||||
|               minLeadingWidth: 48, | ||||
|               title: Text('settingsAprilFoolFeatures').tr(), | ||||
|               contentPadding: const EdgeInsets.only(left: 24, right: 17), | ||||
|               leading: const Icon(Symbols.celebration), | ||||
|               trailing: Switch( | ||||
|                 value: settings.aprilFoolFeatures, | ||||
|                 onChanged: (value) { | ||||
|                   ref | ||||
|                       .read(appSettingsProvider.notifier) | ||||
|                       .setAprilFoolFeatures(value); | ||||
|                 }, | ||||
|               ), | ||||
|             ), | ||||
|             ListTile( | ||||
|               minLeadingWidth: 48, | ||||
|               title: Text('settingsEnterToSend').tr(), | ||||
|               contentPadding: const EdgeInsets.only(left: 24, right: 17), | ||||
|               leading: const Icon(Symbols.send), | ||||
|               trailing: Switch( | ||||
|                 value: settings.enterToSend, | ||||
|                 onChanged: (value) { | ||||
|                   ref.read(appSettingsProvider.notifier).setEnterToSend(value); | ||||
|                 }, | ||||
|               ), | ||||
|             ), | ||||
|           ], | ||||
|         ), | ||||
|       ), | ||||
|   | ||||
		Reference in New Issue
	
	Block a user