✨ Chat enter to send
This commit is contained in:
parent
d257a9697b
commit
4f9bf960d9
@ -271,5 +271,12 @@
|
|||||||
"unreadMessages": {
|
"unreadMessages": {
|
||||||
"one": "{} unread message",
|
"one": "{} unread message",
|
||||||
"other": "{} unread messages"
|
"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
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$chatSummaryHash() => r'fa48d381f489f90055fb728f7e0fda6f8ef49d15';
|
String _$chatSummaryHash() => r'19aad48b5fabb33a76b742400d3b738ceb81c40c';
|
||||||
|
|
||||||
/// See also [ChatSummary].
|
/// See also [ChatSummary].
|
||||||
@ProviderFor(ChatSummary)
|
@ProviderFor(ChatSummary)
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
part 'config.freezed.dart';
|
||||||
|
|
||||||
const kTokenPairStoreKey = 'dyn_user_tk';
|
const kTokenPairStoreKey = 'dyn_user_tk';
|
||||||
|
|
||||||
const kNetworkServerDefault = 'https://nt.solian.app';
|
const kNetworkServerDefault = 'https://nt.solian.app';
|
||||||
@ -21,6 +24,7 @@ const kAppHideBottomNav = 'app_hide_bottom_nav';
|
|||||||
const kAppSoundEffects = 'app_sound_effects';
|
const kAppSoundEffects = 'app_sound_effects';
|
||||||
const kAppAprilFoolFeatures = 'app_april_fool_features';
|
const kAppAprilFoolFeatures = 'app_april_fool_features';
|
||||||
const kAppWindowSize = 'app_window_size';
|
const kAppWindowSize = 'app_window_size';
|
||||||
|
const kAppEnterToSend = 'app_enter_to_send';
|
||||||
|
|
||||||
const Map<String, FilterQuality> kImageQualityLevel = {
|
const Map<String, FilterQuality> kImageQualityLevel = {
|
||||||
'settingsImageQualityLowest': FilterQuality.none,
|
'settingsImageQualityLowest': FilterQuality.none,
|
||||||
@ -46,40 +50,17 @@ final serverUrlProvider = Provider<String>((ref) {
|
|||||||
return prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault;
|
return prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault;
|
||||||
});
|
});
|
||||||
|
|
||||||
class AppSettings {
|
@freezed
|
||||||
final bool realmCompactView;
|
abstract class AppSettings with _$AppSettings {
|
||||||
final bool mixedFeed;
|
const factory AppSettings({
|
||||||
final bool autoTranslate;
|
required bool realmCompactView,
|
||||||
final bool hideBottomNav;
|
required bool mixedFeed,
|
||||||
final bool soundEffects;
|
required bool autoTranslate,
|
||||||
final bool aprilFoolFeatures;
|
required bool hideBottomNav,
|
||||||
|
required bool soundEffects,
|
||||||
AppSettings({
|
required bool aprilFoolFeatures,
|
||||||
required this.realmCompactView,
|
required bool enterToSend,
|
||||||
required this.mixedFeed,
|
}) = _AppSettings;
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppSettingsNotifier extends StateNotifier<AppSettings> {
|
class AppSettingsNotifier extends StateNotifier<AppSettings> {
|
||||||
@ -94,6 +75,7 @@ class AppSettingsNotifier extends StateNotifier<AppSettings> {
|
|||||||
hideBottomNav: prefs.getBool(kAppHideBottomNav) ?? false,
|
hideBottomNav: prefs.getBool(kAppHideBottomNav) ?? false,
|
||||||
soundEffects: prefs.getBool(kAppSoundEffects) ?? true,
|
soundEffects: prefs.getBool(kAppSoundEffects) ?? true,
|
||||||
aprilFoolFeatures: prefs.getBool(kAppAprilFoolFeatures) ?? true,
|
aprilFoolFeatures: prefs.getBool(kAppAprilFoolFeatures) ?? true,
|
||||||
|
enterToSend: prefs.getBool(kAppEnterToSend) ?? true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -126,6 +108,11 @@ class AppSettingsNotifier extends StateNotifier<AppSettings> {
|
|||||||
prefs.setBool(kAppAprilFoolFeatures, value);
|
prefs.setBool(kAppAprilFoolFeatures, value);
|
||||||
state = state.copyWith(aprilFoolFeatures: value);
|
state = state.copyWith(aprilFoolFeatures: value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setEnterToSend(bool value) {
|
||||||
|
prefs.setBool(kAppEnterToSend, value);
|
||||||
|
state = state.copyWith(enterToSend: value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final appSettingsProvider =
|
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,10 +93,13 @@ class ChatRoomListTile extends HookConsumerWidget {
|
|||||||
style: Theme.of(context).textTheme.bodySmall,
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: Text(
|
||||||
RelativeTime(context).format(data.lastMessage.createdAt),
|
RelativeTime(context).format(data.lastMessage.createdAt),
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -117,24 +120,14 @@ 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(
|
return ListTile(
|
||||||
leading:
|
leading: Badge(
|
||||||
|
isLabelVisible: summary.when(
|
||||||
|
data: (data) => (data?.unreadCount ?? 0) > 0,
|
||||||
|
loading: () => false,
|
||||||
|
error: (_, __) => false,
|
||||||
|
),
|
||||||
|
child:
|
||||||
(isDirect && room.pictureId == null)
|
(isDirect && room.pictureId == null)
|
||||||
? SplitAvatarWidget(
|
? SplitAvatarWidget(
|
||||||
filesId:
|
filesId:
|
||||||
@ -145,13 +138,13 @@ class ChatRoomListTile extends HookConsumerWidget {
|
|||||||
: room.pictureId == null
|
: room.pictureId == null
|
||||||
? CircleAvatar(child: Text(room.name![0].toUpperCase()))
|
? CircleAvatar(child: Text(room.name![0].toUpperCase()))
|
||||||
: ProfilePictureWidget(fileId: room.pictureId),
|
: ProfilePictureWidget(fileId: room.pictureId),
|
||||||
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
(isDirect && room.name == null)
|
(isDirect && room.name == null)
|
||||||
? room.members!.map((e) => e.account.nick).join(', ')
|
? room.members!.map((e) => e.account.nick).join(', ')
|
||||||
: room.name ?? '',
|
: room.name ?? '',
|
||||||
),
|
),
|
||||||
subtitle: buildSubtitle(),
|
subtitle: buildSubtitle(),
|
||||||
trailing: buildTrailing(),
|
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
// Clear unread count if there are unread messages
|
// Clear unread count if there are unread messages
|
||||||
final summary = await ref.read(chatSummaryProvider.future);
|
final summary = await ref.read(chatSummaryProvider.future);
|
||||||
|
@ -3,6 +3,7 @@ import 'dart:convert';
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.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 TextEditingController messageController;
|
||||||
final SnChatRoom chatRoom;
|
final SnChatRoom chatRoom;
|
||||||
final VoidCallback onSend;
|
final VoidCallback onSend;
|
||||||
@ -705,8 +706,26 @@ class _ChatInput extends StatelessWidget {
|
|||||||
required this.onMoveAttachment,
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final enterToSend = ref.watch(appSettingsProvider).enterToSend;
|
||||||
|
|
||||||
return Material(
|
return Material(
|
||||||
elevation: 8,
|
elevation: 8,
|
||||||
color: Theme.of(context).colorScheme.surface,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
@ -806,8 +825,20 @@ class _ChatInput extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
child: RawKeyboardListener(
|
||||||
|
focusNode: FocusNode(),
|
||||||
|
onKey: (event) => _handleKeyPress(context, ref, event),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: messageController,
|
controller: messageController,
|
||||||
|
inputFormatters: [
|
||||||
|
if (enterToSend)
|
||||||
|
TextInputFormatter.withFunction((oldValue, newValue) {
|
||||||
|
if (newValue.text.endsWith('\n')) {
|
||||||
|
return oldValue;
|
||||||
|
}
|
||||||
|
return newValue;
|
||||||
|
}),
|
||||||
|
],
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText:
|
hintText:
|
||||||
(chatRoom.type == 1 && chatRoom.name == null)
|
(chatRoom.type == 1 && chatRoom.name == null)
|
||||||
@ -829,7 +860,7 @@ class _ChatInput extends StatelessWidget {
|
|||||||
maxLines: null,
|
maxLines: null,
|
||||||
onTapOutside:
|
onTapOutside:
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
onSubmitted: (_) => onSend(),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
|
@ -26,6 +26,7 @@ class SettingsScreen extends HookConsumerWidget {
|
|||||||
final serverUrl = ref.watch(serverUrlProvider);
|
final serverUrl = ref.watch(serverUrlProvider);
|
||||||
final prefs = ref.watch(sharedPreferencesProvider);
|
final prefs = ref.watch(sharedPreferencesProvider);
|
||||||
final controller = TextEditingController(text: serverUrl);
|
final controller = TextEditingController(text: serverUrl);
|
||||||
|
final settings = ref.watch(appSettingsProvider);
|
||||||
|
|
||||||
final docBasepath = useState<String?>(null);
|
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);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user