Compare commits
	
		
			2 Commits
		
	
	
		
			38f8103265
			...
			b0f3b6b5c3
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| b0f3b6b5c3 | |||
| cb2af379fa | 
| @@ -473,6 +473,7 @@ | ||||
|   "settingsKeyboardShortcutSettings": "Settings", | ||||
|   "settingsKeyboardShortcutNewMessage": "New Message", | ||||
|   "settingsKeyboardShortcutCloseDialog": "Close Dialog", | ||||
|   "settingsMessageDisplayStyle": "Message Display Style", | ||||
|   "close": "Close", | ||||
|   "drafts": "Drafts", | ||||
|   "noDrafts": "No drafts yet", | ||||
| @@ -1021,5 +1022,6 @@ | ||||
|   "currentEmbed": "Current Embed", | ||||
|   "noEmbed": "No embed yet", | ||||
|   "save": "Save", | ||||
|   "webView": "Web View" | ||||
|   "webView": "Web View", | ||||
|   "messageActions": "Message Actions" | ||||
| } | ||||
|   | ||||
| @@ -26,6 +26,7 @@ const kAppAprilFoolFeatures = 'app_april_fool_features'; | ||||
| const kAppWindowSize = 'app_window_size'; | ||||
| const kAppEnterToSend = 'app_enter_to_send'; | ||||
| const kAppDefaultPoolId = 'app_default_pool_id'; | ||||
| const kAppMessageDisplayStyle = 'app_message_display_style'; | ||||
| const kFeaturedPostsCollapsedId = | ||||
|     'featured_posts_collapsed_id'; // Key for storing the ID of the collapsed featured post | ||||
|  | ||||
| @@ -67,6 +68,7 @@ sealed class AppSettings with _$AppSettings { | ||||
|     required int? appColorScheme, // The color stored via the int type | ||||
|     required Size? windowSize, // The window size for desktop platforms | ||||
|     required String? defaultPoolId, | ||||
|     required String messageDisplayStyle, | ||||
|   }) = _AppSettings; | ||||
| } | ||||
|  | ||||
| @@ -87,6 +89,7 @@ class AppSettingsNotifier extends _$AppSettingsNotifier { | ||||
|       appColorScheme: prefs.getInt(kAppColorSchemeStoreKey), | ||||
|       windowSize: _getWindowSizeFromPrefs(prefs), | ||||
|       defaultPoolId: prefs.getString(kAppDefaultPoolId), | ||||
|       messageDisplayStyle: prefs.getString(kAppMessageDisplayStyle) ?? 'bubble', | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| @@ -106,6 +109,7 @@ class AppSettingsNotifier extends _$AppSettingsNotifier { | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   void setDefaultPoolId(String? value) { | ||||
|     final prefs = ref.read(sharedPreferencesProvider); | ||||
|     if (value != null) { | ||||
| @@ -122,7 +126,7 @@ class AppSettingsNotifier extends _$AppSettingsNotifier { | ||||
|     state = state.copyWith(autoTranslate: value); | ||||
|   } | ||||
|  | ||||
|   void setDataSavingMode(bool value){ | ||||
|   void setDataSavingMode(bool value) { | ||||
|     final prefs = ref.read(sharedPreferencesProvider); | ||||
|     prefs.setBool(kAppDataSavingMode, value); | ||||
|     state = state.copyWith(dataSavingMode: value); | ||||
| @@ -186,6 +190,12 @@ class AppSettingsNotifier extends _$AppSettingsNotifier { | ||||
|   Size? getWindowSize() { | ||||
|     return state.windowSize; | ||||
|   } | ||||
|  | ||||
|   void setMessageDisplayStyle(String value) { | ||||
|     final prefs = ref.read(sharedPreferencesProvider); | ||||
|     prefs.setString(kAppMessageDisplayStyle, value); | ||||
|     state = state.copyWith(messageDisplayStyle: value); | ||||
|   } | ||||
| } | ||||
|  | ||||
| final updateInfoProvider = | ||||
|   | ||||
| @@ -16,7 +16,7 @@ mixin _$AppSettings { | ||||
|  | ||||
|  bool get autoTranslate; bool get dataSavingMode; bool get soundEffects; bool get aprilFoolFeatures; bool get enterToSend; bool get appBarTransparent; bool get showBackgroundImage; String? get customFonts; int? get appColorScheme;// The color stored via the int type | ||||
|  Size? get windowSize;// The window size for desktop platforms | ||||
|  String? get defaultPoolId; | ||||
|  String? get defaultPoolId; String get messageDisplayStyle; | ||||
| /// Create a copy of AppSettings | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @JsonKey(includeFromJson: false, includeToJson: false) | ||||
| @@ -27,16 +27,16 @@ $AppSettingsCopyWith<AppSettings> get copyWith => _$AppSettingsCopyWithImpl<AppS | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.dataSavingMode, dataSavingMode) || other.dataSavingMode == dataSavingMode)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.showBackgroundImage, showBackgroundImage) || other.showBackgroundImage == showBackgroundImage)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize)&&(identical(other.defaultPoolId, defaultPoolId) || other.defaultPoolId == defaultPoolId)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.dataSavingMode, dataSavingMode) || other.dataSavingMode == dataSavingMode)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.showBackgroundImage, showBackgroundImage) || other.showBackgroundImage == showBackgroundImage)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize)&&(identical(other.defaultPoolId, defaultPoolId) || other.defaultPoolId == defaultPoolId)&&(identical(other.messageDisplayStyle, messageDisplayStyle) || other.messageDisplayStyle == messageDisplayStyle)); | ||||
| } | ||||
|  | ||||
|  | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,autoTranslate,dataSavingMode,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,showBackgroundImage,customFonts,appColorScheme,windowSize,defaultPoolId); | ||||
| int get hashCode => Object.hash(runtimeType,autoTranslate,dataSavingMode,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,showBackgroundImage,customFonts,appColorScheme,windowSize,defaultPoolId,messageDisplayStyle); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'AppSettings(autoTranslate: $autoTranslate, dataSavingMode: $dataSavingMode, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, customFonts: $customFonts, appColorScheme: $appColorScheme, windowSize: $windowSize, defaultPoolId: $defaultPoolId)'; | ||||
|   return 'AppSettings(autoTranslate: $autoTranslate, dataSavingMode: $dataSavingMode, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, customFonts: $customFonts, appColorScheme: $appColorScheme, windowSize: $windowSize, defaultPoolId: $defaultPoolId, messageDisplayStyle: $messageDisplayStyle)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -47,7 +47,7 @@ abstract mixin class $AppSettingsCopyWith<$Res>  { | ||||
|   factory $AppSettingsCopyWith(AppSettings value, $Res Function(AppSettings) _then) = _$AppSettingsCopyWithImpl; | ||||
| @useResult | ||||
| $Res call({ | ||||
|  bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize, String? defaultPoolId | ||||
|  bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize, String? defaultPoolId, String messageDisplayStyle | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -64,7 +64,7 @@ class _$AppSettingsCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of AppSettings | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? autoTranslate = null,Object? dataSavingMode = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,Object? defaultPoolId = freezed,}) { | ||||
| @pragma('vm:prefer-inline') @override $Res call({Object? autoTranslate = null,Object? dataSavingMode = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,Object? defaultPoolId = freezed,Object? messageDisplayStyle = null,}) { | ||||
|   return _then(_self.copyWith( | ||||
| autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable | ||||
| as bool,dataSavingMode: null == dataSavingMode ? _self.dataSavingMode : dataSavingMode // ignore: cast_nullable_to_non_nullable | ||||
| @@ -77,7 +77,8 @@ as bool,customFonts: freezed == customFonts ? _self.customFonts : customFonts // | ||||
| as String?,appColorScheme: freezed == appColorScheme ? _self.appColorScheme : appColorScheme // ignore: cast_nullable_to_non_nullable | ||||
| as int?,windowSize: freezed == windowSize ? _self.windowSize : windowSize // ignore: cast_nullable_to_non_nullable | ||||
| as Size?,defaultPoolId: freezed == defaultPoolId ? _self.defaultPoolId : defaultPoolId // ignore: cast_nullable_to_non_nullable | ||||
| as String?, | ||||
| as String?,messageDisplayStyle: null == messageDisplayStyle ? _self.messageDisplayStyle : messageDisplayStyle // ignore: cast_nullable_to_non_nullable | ||||
| as String, | ||||
|   )); | ||||
| } | ||||
|  | ||||
| @@ -159,10 +160,10 @@ return $default(_that);case _: | ||||
| /// } | ||||
| /// ``` | ||||
|  | ||||
| @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool autoTranslate,  bool dataSavingMode,  bool soundEffects,  bool aprilFoolFeatures,  bool enterToSend,  bool appBarTransparent,  bool showBackgroundImage,  String? customFonts,  int? appColorScheme,  Size? windowSize,  String? defaultPoolId)?  $default,{required TResult orElse(),}) {final _that = this; | ||||
| @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool autoTranslate,  bool dataSavingMode,  bool soundEffects,  bool aprilFoolFeatures,  bool enterToSend,  bool appBarTransparent,  bool showBackgroundImage,  String? customFonts,  int? appColorScheme,  Size? windowSize,  String? defaultPoolId,  String messageDisplayStyle)?  $default,{required TResult orElse(),}) {final _that = this; | ||||
| switch (_that) { | ||||
| case _AppSettings() when $default != null: | ||||
| return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize,_that.defaultPoolId);case _: | ||||
| return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize,_that.defaultPoolId,_that.messageDisplayStyle);case _: | ||||
|   return orElse(); | ||||
|  | ||||
| } | ||||
| @@ -180,10 +181,10 @@ return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_tha | ||||
| /// } | ||||
| /// ``` | ||||
|  | ||||
| @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool autoTranslate,  bool dataSavingMode,  bool soundEffects,  bool aprilFoolFeatures,  bool enterToSend,  bool appBarTransparent,  bool showBackgroundImage,  String? customFonts,  int? appColorScheme,  Size? windowSize,  String? defaultPoolId)  $default,) {final _that = this; | ||||
| @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool autoTranslate,  bool dataSavingMode,  bool soundEffects,  bool aprilFoolFeatures,  bool enterToSend,  bool appBarTransparent,  bool showBackgroundImage,  String? customFonts,  int? appColorScheme,  Size? windowSize,  String? defaultPoolId,  String messageDisplayStyle)  $default,) {final _that = this; | ||||
| switch (_that) { | ||||
| case _AppSettings(): | ||||
| return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize,_that.defaultPoolId);} | ||||
| return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize,_that.defaultPoolId,_that.messageDisplayStyle);} | ||||
| } | ||||
| /// A variant of `when` that fallback to returning `null` | ||||
| /// | ||||
| @@ -197,10 +198,10 @@ return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_tha | ||||
| /// } | ||||
| /// ``` | ||||
|  | ||||
| @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool autoTranslate,  bool dataSavingMode,  bool soundEffects,  bool aprilFoolFeatures,  bool enterToSend,  bool appBarTransparent,  bool showBackgroundImage,  String? customFonts,  int? appColorScheme,  Size? windowSize,  String? defaultPoolId)?  $default,) {final _that = this; | ||||
| @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool autoTranslate,  bool dataSavingMode,  bool soundEffects,  bool aprilFoolFeatures,  bool enterToSend,  bool appBarTransparent,  bool showBackgroundImage,  String? customFonts,  int? appColorScheme,  Size? windowSize,  String? defaultPoolId,  String messageDisplayStyle)?  $default,) {final _that = this; | ||||
| switch (_that) { | ||||
| case _AppSettings() when $default != null: | ||||
| return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize,_that.defaultPoolId);case _: | ||||
| return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_that.aprilFoolFeatures,_that.enterToSend,_that.appBarTransparent,_that.showBackgroundImage,_that.customFonts,_that.appColorScheme,_that.windowSize,_that.defaultPoolId,_that.messageDisplayStyle);case _: | ||||
|   return null; | ||||
|  | ||||
| } | ||||
| @@ -212,7 +213,7 @@ return $default(_that.autoTranslate,_that.dataSavingMode,_that.soundEffects,_tha | ||||
|  | ||||
|  | ||||
| class _AppSettings implements AppSettings { | ||||
|   const _AppSettings({required this.autoTranslate, required this.dataSavingMode, required this.soundEffects, required this.aprilFoolFeatures, required this.enterToSend, required this.appBarTransparent, required this.showBackgroundImage, required this.customFonts, required this.appColorScheme, required this.windowSize, required this.defaultPoolId}); | ||||
|   const _AppSettings({required this.autoTranslate, required this.dataSavingMode, required this.soundEffects, required this.aprilFoolFeatures, required this.enterToSend, required this.appBarTransparent, required this.showBackgroundImage, required this.customFonts, required this.appColorScheme, required this.windowSize, required this.defaultPoolId, required this.messageDisplayStyle}); | ||||
|    | ||||
|  | ||||
| @override final  bool autoTranslate; | ||||
| @@ -228,6 +229,7 @@ class _AppSettings implements AppSettings { | ||||
| @override final  Size? windowSize; | ||||
| // The window size for desktop platforms | ||||
| @override final  String? defaultPoolId; | ||||
| @override final  String messageDisplayStyle; | ||||
|  | ||||
| /// Create a copy of AppSettings | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @@ -239,16 +241,16 @@ _$AppSettingsCopyWith<_AppSettings> get copyWith => __$AppSettingsCopyWithImpl<_ | ||||
|  | ||||
| @override | ||||
| bool operator ==(Object other) { | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.dataSavingMode, dataSavingMode) || other.dataSavingMode == dataSavingMode)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.showBackgroundImage, showBackgroundImage) || other.showBackgroundImage == showBackgroundImage)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize)&&(identical(other.defaultPoolId, defaultPoolId) || other.defaultPoolId == defaultPoolId)); | ||||
|   return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettings&&(identical(other.autoTranslate, autoTranslate) || other.autoTranslate == autoTranslate)&&(identical(other.dataSavingMode, dataSavingMode) || other.dataSavingMode == dataSavingMode)&&(identical(other.soundEffects, soundEffects) || other.soundEffects == soundEffects)&&(identical(other.aprilFoolFeatures, aprilFoolFeatures) || other.aprilFoolFeatures == aprilFoolFeatures)&&(identical(other.enterToSend, enterToSend) || other.enterToSend == enterToSend)&&(identical(other.appBarTransparent, appBarTransparent) || other.appBarTransparent == appBarTransparent)&&(identical(other.showBackgroundImage, showBackgroundImage) || other.showBackgroundImage == showBackgroundImage)&&(identical(other.customFonts, customFonts) || other.customFonts == customFonts)&&(identical(other.appColorScheme, appColorScheme) || other.appColorScheme == appColorScheme)&&(identical(other.windowSize, windowSize) || other.windowSize == windowSize)&&(identical(other.defaultPoolId, defaultPoolId) || other.defaultPoolId == defaultPoolId)&&(identical(other.messageDisplayStyle, messageDisplayStyle) || other.messageDisplayStyle == messageDisplayStyle)); | ||||
| } | ||||
|  | ||||
|  | ||||
| @override | ||||
| int get hashCode => Object.hash(runtimeType,autoTranslate,dataSavingMode,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,showBackgroundImage,customFonts,appColorScheme,windowSize,defaultPoolId); | ||||
| int get hashCode => Object.hash(runtimeType,autoTranslate,dataSavingMode,soundEffects,aprilFoolFeatures,enterToSend,appBarTransparent,showBackgroundImage,customFonts,appColorScheme,windowSize,defaultPoolId,messageDisplayStyle); | ||||
|  | ||||
| @override | ||||
| String toString() { | ||||
|   return 'AppSettings(autoTranslate: $autoTranslate, dataSavingMode: $dataSavingMode, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, customFonts: $customFonts, appColorScheme: $appColorScheme, windowSize: $windowSize, defaultPoolId: $defaultPoolId)'; | ||||
|   return 'AppSettings(autoTranslate: $autoTranslate, dataSavingMode: $dataSavingMode, soundEffects: $soundEffects, aprilFoolFeatures: $aprilFoolFeatures, enterToSend: $enterToSend, appBarTransparent: $appBarTransparent, showBackgroundImage: $showBackgroundImage, customFonts: $customFonts, appColorScheme: $appColorScheme, windowSize: $windowSize, defaultPoolId: $defaultPoolId, messageDisplayStyle: $messageDisplayStyle)'; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -259,7 +261,7 @@ abstract mixin class _$AppSettingsCopyWith<$Res> implements $AppSettingsCopyWith | ||||
|   factory _$AppSettingsCopyWith(_AppSettings value, $Res Function(_AppSettings) _then) = __$AppSettingsCopyWithImpl; | ||||
| @override @useResult | ||||
| $Res call({ | ||||
|  bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize, String? defaultPoolId | ||||
|  bool autoTranslate, bool dataSavingMode, bool soundEffects, bool aprilFoolFeatures, bool enterToSend, bool appBarTransparent, bool showBackgroundImage, String? customFonts, int? appColorScheme, Size? windowSize, String? defaultPoolId, String messageDisplayStyle | ||||
| }); | ||||
|  | ||||
|  | ||||
| @@ -276,7 +278,7 @@ class __$AppSettingsCopyWithImpl<$Res> | ||||
|  | ||||
| /// Create a copy of AppSettings | ||||
| /// with the given fields replaced by the non-null parameter values. | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? autoTranslate = null,Object? dataSavingMode = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,Object? defaultPoolId = freezed,}) { | ||||
| @override @pragma('vm:prefer-inline') $Res call({Object? autoTranslate = null,Object? dataSavingMode = null,Object? soundEffects = null,Object? aprilFoolFeatures = null,Object? enterToSend = null,Object? appBarTransparent = null,Object? showBackgroundImage = null,Object? customFonts = freezed,Object? appColorScheme = freezed,Object? windowSize = freezed,Object? defaultPoolId = freezed,Object? messageDisplayStyle = null,}) { | ||||
|   return _then(_AppSettings( | ||||
| autoTranslate: null == autoTranslate ? _self.autoTranslate : autoTranslate // ignore: cast_nullable_to_non_nullable | ||||
| as bool,dataSavingMode: null == dataSavingMode ? _self.dataSavingMode : dataSavingMode // ignore: cast_nullable_to_non_nullable | ||||
| @@ -289,7 +291,8 @@ as bool,customFonts: freezed == customFonts ? _self.customFonts : customFonts // | ||||
| as String?,appColorScheme: freezed == appColorScheme ? _self.appColorScheme : appColorScheme // ignore: cast_nullable_to_non_nullable | ||||
| as int?,windowSize: freezed == windowSize ? _self.windowSize : windowSize // ignore: cast_nullable_to_non_nullable | ||||
| as Size?,defaultPoolId: freezed == defaultPoolId ? _self.defaultPoolId : defaultPoolId // ignore: cast_nullable_to_non_nullable | ||||
| as String?, | ||||
| as String?,messageDisplayStyle: null == messageDisplayStyle ? _self.messageDisplayStyle : messageDisplayStyle // ignore: cast_nullable_to_non_nullable | ||||
| as String, | ||||
|   )); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -7,7 +7,7 @@ part of 'config.dart'; | ||||
| // ************************************************************************** | ||||
|  | ||||
| String _$appSettingsNotifierHash() => | ||||
|     r'a623ad859b71f42d0527b7f8b75bd37a6fd5d5c7'; | ||||
|     r'9f0979f18b107e61185391e7c39bd81ac4b8ca50'; | ||||
|  | ||||
| /// See also [AppSettingsNotifier]. | ||||
| @ProviderFor(AppSettingsNotifier) | ||||
|   | ||||
| @@ -12,6 +12,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:image_picker/image_picker.dart'; | ||||
| import 'package:island/pods/network.dart'; | ||||
| import 'package:island/pods/userinfo.dart'; | ||||
| import 'package:island/services/color_extraction.dart'; | ||||
| import 'package:island/services/responsive.dart'; | ||||
| import 'package:island/widgets/alert.dart'; | ||||
| @@ -35,7 +36,8 @@ class SettingsScreen extends HookConsumerWidget { | ||||
|     final isDesktop = | ||||
|         !kIsWeb && (Platform.isWindows || Platform.isMacOS || Platform.isLinux); | ||||
|     final isWide = isWideScreen(context); | ||||
|     final poolsAsync = ref.watch(poolsProvider); | ||||
|     final pools = ref.watch(poolsProvider); | ||||
|     final user = ref.watch(userInfoProvider); | ||||
|     final docBasepath = useState<String?>(null); | ||||
|  | ||||
|     useEffect(() { | ||||
| @@ -129,6 +131,48 @@ class SettingsScreen extends HookConsumerWidget { | ||||
|         ), | ||||
|       ), | ||||
|  | ||||
|       // Message display style settings | ||||
|       ListTile( | ||||
|         minLeadingWidth: 48, | ||||
|         title: Text('settingsMessageDisplayStyle').tr(), | ||||
|         contentPadding: const EdgeInsets.only(left: 24, right: 17), | ||||
|         leading: const Icon(Symbols.chat), | ||||
|         trailing: DropdownButtonHideUnderline( | ||||
|           child: DropdownButton2<String>( | ||||
|             isExpanded: true, | ||||
|             items: [ | ||||
|               DropdownMenuItem<String>( | ||||
|                 value: 'bubble', | ||||
|                 child: Text('Bubble').fontSize(14), | ||||
|               ), | ||||
|               DropdownMenuItem<String>( | ||||
|                 value: 'discord', | ||||
|                 child: Text('Discord').fontSize(14), | ||||
|               ), | ||||
|               DropdownMenuItem<String>( | ||||
|                 value: 'irc', | ||||
|                 child: Text('IRC').fontSize(14), | ||||
|               ), | ||||
|             ], | ||||
|             value: settings.messageDisplayStyle, | ||||
|             onChanged: (String? value) { | ||||
|               if (value != null) { | ||||
|                 ref | ||||
|                     .read(appSettingsNotifierProvider.notifier) | ||||
|                     .setMessageDisplayStyle(value); | ||||
|                 showSnackBar('settingsApplied'.tr()); | ||||
|               } | ||||
|             }, | ||||
|             buttonStyleData: const ButtonStyleData( | ||||
|               padding: EdgeInsets.symmetric(horizontal: 16, vertical: 5), | ||||
|               height: 40, | ||||
|               width: 140, | ||||
|             ), | ||||
|             menuItemStyleData: const MenuItemStyleData(height: 40), | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|  | ||||
|       // Color scheme settings | ||||
|       ListTile( | ||||
|         minLeadingWidth: 48, | ||||
| @@ -370,10 +414,11 @@ class SettingsScreen extends HookConsumerWidget { | ||||
|         ), | ||||
|       ), | ||||
|  | ||||
|       poolsAsync.when( | ||||
|         data: (pools) { | ||||
|           final validPools = pools.filterValid(); | ||||
|           final currentPoolId = resolveDefaultPoolId(ref, pools); | ||||
|       if (user.value != null) | ||||
|         pools.when( | ||||
|           data: (data) { | ||||
|             final validPools = data.filterValid(); | ||||
|             final currentPoolId = resolveDefaultPoolId(ref, data); | ||||
|  | ||||
|             return ListTile( | ||||
|               isThreeLine: true, | ||||
| @@ -382,9 +427,6 @@ class SettingsScreen extends HookConsumerWidget { | ||||
|               contentPadding: const EdgeInsets.only(left: 24, right: 17), | ||||
|               leading: const Icon(Symbols.cloud), | ||||
|               subtitle: Text( | ||||
|               validPools | ||||
|                       .firstWhereOrNull((p) => p.id == currentPoolId) | ||||
|                       ?.description ?? | ||||
|                 'settingsDefaultPoolHelper'.tr(), | ||||
|                 style: Theme.of(context).textTheme.bodySmall, | ||||
|               ), | ||||
| @@ -395,7 +437,11 @@ class SettingsScreen extends HookConsumerWidget { | ||||
|                       validPools.map((p) { | ||||
|                         return DropdownMenuItem<String>( | ||||
|                           value: p.id, | ||||
|                         child: Text(p.name).fontSize(14), | ||||
|                           child: Text( | ||||
|                             p.name, | ||||
|                             maxLines: 1, | ||||
|                             overflow: TextOverflow.ellipsis, | ||||
|                           ).fontSize(14), | ||||
|                         ); | ||||
|                       }).toList(), | ||||
|                   value: currentPoolId, | ||||
| @@ -408,7 +454,7 @@ class SettingsScreen extends HookConsumerWidget { | ||||
|                   buttonStyleData: const ButtonStyleData( | ||||
|                     padding: EdgeInsets.symmetric(horizontal: 16, vertical: 5), | ||||
|                     height: 40, | ||||
|                   width: 220, | ||||
|                     width: 120, | ||||
|                   ), | ||||
|                   menuItemStyleData: const MenuItemStyleData(height: 40), | ||||
|                 ), | ||||
|   | ||||
| @@ -18,6 +18,32 @@ class MessageContent extends StatelessWidget { | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     if (item.type == 'messages.delete' || item.deletedAt != null) { | ||||
|       return Row( | ||||
|         mainAxisSize: MainAxisSize.min, | ||||
|         crossAxisAlignment: CrossAxisAlignment.center, | ||||
|         children: [ | ||||
|           Icon( | ||||
|             Symbols.delete, | ||||
|             size: 14, | ||||
|             color: Theme.of( | ||||
|               context, | ||||
|             ).colorScheme.onSurfaceVariant.withOpacity(0.6), | ||||
|           ), | ||||
|           const Gap(4), | ||||
|           Text( | ||||
|             item.content ?? 'Deleted a message', | ||||
|             style: Theme.of(context).textTheme.bodySmall?.copyWith( | ||||
|               color: Theme.of( | ||||
|                 context, | ||||
|               ).colorScheme.onSurfaceVariant.withOpacity(0.6), | ||||
|               fontStyle: FontStyle.italic, | ||||
|             ), | ||||
|           ), | ||||
|         ], | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     switch (item.type) { | ||||
|       case 'call.start': | ||||
|       case 'call.ended': | ||||
| @@ -71,30 +97,6 @@ class MessageContent extends StatelessWidget { | ||||
|               ), | ||||
|           ], | ||||
|         ); | ||||
|       case 'messages.delete': | ||||
|         return Row( | ||||
|           mainAxisSize: MainAxisSize.min, | ||||
|           crossAxisAlignment: CrossAxisAlignment.center, | ||||
|           children: [ | ||||
|             Icon( | ||||
|               Symbols.delete, | ||||
|               size: 14, | ||||
|               color: Theme.of( | ||||
|                 context, | ||||
|               ).colorScheme.onSurfaceVariant.withOpacity(0.6), | ||||
|             ), | ||||
|             const Gap(4), | ||||
|             Text( | ||||
|               item.content ?? 'Deleted a message', | ||||
|               style: Theme.of(context).textTheme.bodySmall?.copyWith( | ||||
|                 color: Theme.of( | ||||
|                   context, | ||||
|                 ).colorScheme.onSurfaceVariant.withOpacity(0.6), | ||||
|                 fontStyle: FontStyle.italic, | ||||
|               ), | ||||
|             ), | ||||
|           ], | ||||
|         ); | ||||
|       case 'text': | ||||
|       default: | ||||
|         return Column( | ||||
|   | ||||
| @@ -13,18 +13,21 @@ import 'package:island/database/message.dart'; | ||||
| import 'package:island/models/embed.dart'; | ||||
| import 'package:island/pods/messages_notifier.dart'; | ||||
| import 'package:island/pods/translate.dart'; | ||||
| import 'package:island/pods/config.dart'; | ||||
| import 'package:island/screens/chat/room.dart'; | ||||
| import 'package:island/utils/mapping.dart'; | ||||
| import 'package:island/widgets/account/account_pfc.dart'; | ||||
| import 'package:island/widgets/app_scaffold.dart'; | ||||
| import 'package:island/widgets/chat/message_content.dart'; | ||||
| import 'package:island/widgets/chat/message_indicators.dart'; | ||||
| import 'package:island/widgets/chat/message_sender_info.dart'; | ||||
| import 'package:island/widgets/content/alert.native.dart'; | ||||
| import 'package:island/widgets/content/cloud_file_collection.dart'; | ||||
| import 'package:island/widgets/content/cloud_files.dart'; | ||||
| import 'package:island/widgets/content/embed/link.dart'; | ||||
| import 'package:material_symbols_icons/material_symbols_icons.dart'; | ||||
| import 'package:styled_widget/styled_widget.dart'; | ||||
| import 'package:super_context_menu/super_context_menu.dart'; | ||||
| import 'package:island/widgets/content/sheet.dart'; | ||||
|  | ||||
| class MessageItemAction { | ||||
|   static const String edit = "edit"; | ||||
| @@ -51,6 +54,189 @@ class MessageItem extends HookConsumerWidget { | ||||
|     required this.onJump, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final remoteMessage = message.toRemoteMessage(); | ||||
|     final settings = ref.watch(appSettingsNotifierProvider); | ||||
|  | ||||
|     final isMobile = !kIsWeb && (Platform.isAndroid || Platform.isIOS); | ||||
|  | ||||
|     final messageLanguage = | ||||
|         remoteMessage.content != null | ||||
|             ? ref.watch(detectStringLanguageProvider(remoteMessage.content!)) | ||||
|             : null; | ||||
|  | ||||
|     final currentLanguage = context.locale.toString(); | ||||
|     final translatableLanguage = | ||||
|         messageLanguage != null | ||||
|             ? messageLanguage.substring(0, 2) != currentLanguage.substring(0, 2) | ||||
|             : false; | ||||
|  | ||||
|     final translating = useState(false); | ||||
|     final translatedText = useState<String?>(null); | ||||
|  | ||||
|     Future<void> translate() async { | ||||
|       if (translatedText.value != null) { | ||||
|         translatedText.value = null; | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       if (translating.value) return; | ||||
|       if (remoteMessage.content == null) return; | ||||
|       translating.value = true; | ||||
|       try { | ||||
|         final text = await ref.watch( | ||||
|           translateStringProvider( | ||||
|             TranslateQuery( | ||||
|               text: remoteMessage.content!, | ||||
|               lang: currentLanguage.substring(0, 2), | ||||
|             ), | ||||
|           ).future, | ||||
|         ); | ||||
|         translatedText.value = text; | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|       } finally { | ||||
|         translating.value = false; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     void showActionMenu() { | ||||
|       if (onAction == null) return; | ||||
|       showModalBottomSheet( | ||||
|         context: context, | ||||
|         builder: | ||||
|             (context) => SheetScaffold( | ||||
|               titleText: 'messageActions'.tr(), | ||||
|               child: SingleChildScrollView( | ||||
|                 child: Column( | ||||
|                   children: [ | ||||
|                     if (isCurrentUser) | ||||
|                       ListTile( | ||||
|                         leading: Icon(Symbols.edit), | ||||
|                         title: Text('edit'.tr()), | ||||
|                         onTap: () { | ||||
|                           onAction!.call(MessageItemAction.edit); | ||||
|                           Navigator.pop(context); | ||||
|                         }, | ||||
|                       ), | ||||
|                     if (isCurrentUser) | ||||
|                       ListTile( | ||||
|                         leading: Icon(Symbols.delete), | ||||
|                         title: Text('delete'.tr()), | ||||
|                         onTap: () { | ||||
|                           onAction!.call(MessageItemAction.delete); | ||||
|                           Navigator.pop(context); | ||||
|                         }, | ||||
|                       ), | ||||
|                     if (isCurrentUser) Divider(), | ||||
|                     ListTile( | ||||
|                       leading: Icon(Symbols.reply), | ||||
|                       title: Text('reply'.tr()), | ||||
|                       onTap: () { | ||||
|                         onAction!.call(MessageItemAction.reply); | ||||
|                         Navigator.pop(context); | ||||
|                       }, | ||||
|                     ), | ||||
|                     ListTile( | ||||
|                       leading: Icon(Symbols.forward), | ||||
|                       title: Text('forward'.tr()), | ||||
|                       onTap: () { | ||||
|                         onAction!.call(MessageItemAction.forward); | ||||
|                         Navigator.pop(context); | ||||
|                       }, | ||||
|                     ), | ||||
|                     if (translatableLanguage) Divider(), | ||||
|                     if (translatableLanguage) | ||||
|                       ListTile( | ||||
|                         leading: Icon(Symbols.translate), | ||||
|                         title: Text( | ||||
|                           translatedText.value == null | ||||
|                               ? 'translate'.tr() | ||||
|                               : translating.value | ||||
|                               ? 'translating'.tr() | ||||
|                               : 'translated'.tr(), | ||||
|                         ), | ||||
|                         onTap: () { | ||||
|                           translate(); | ||||
|                           Navigator.pop(context); | ||||
|                         }, | ||||
|                       ), | ||||
|                     if (isMobile) Divider(), | ||||
|                     if (isMobile) | ||||
|                       ListTile( | ||||
|                         leading: Icon(Symbols.copy_all), | ||||
|                         title: Text('copyMessage'.tr()), | ||||
|                         onTap: () { | ||||
|                           Clipboard.setData( | ||||
|                             ClipboardData(text: remoteMessage.content ?? ''), | ||||
|                           ); | ||||
|                           Navigator.pop(context); | ||||
|                         }, | ||||
|                       ), | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return GestureDetector( | ||||
|       onLongPress: showActionMenu, | ||||
|       onSecondaryTap: showActionMenu, | ||||
|       child: switch (settings.messageDisplayStyle) { | ||||
|         'irc' => MessageItemDisplayIRC( | ||||
|           message: message, | ||||
|           isCurrentUser: isCurrentUser, | ||||
|           progress: progress, | ||||
|           showAvatar: showAvatar, | ||||
|           onJump: onJump, | ||||
|           translatedText: translatedText.value, | ||||
|           translating: translating.value, | ||||
|         ), | ||||
|         'discord' => MessageItemDisplayDiscord( | ||||
|           message: message, | ||||
|           isCurrentUser: isCurrentUser, | ||||
|           progress: progress, | ||||
|           showAvatar: showAvatar, | ||||
|           onJump: onJump, | ||||
|           translatedText: translatedText.value, | ||||
|           translating: translating.value, | ||||
|         ), | ||||
|         _ => MessageItemDisplayBubble( | ||||
|           message: message, | ||||
|           isCurrentUser: isCurrentUser, | ||||
|           progress: progress, | ||||
|           showAvatar: showAvatar, | ||||
|           onJump: onJump, | ||||
|           translatedText: translatedText.value, | ||||
|           translating: translating.value, | ||||
|         ), | ||||
|       }, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class MessageItemDisplayBubble extends HookConsumerWidget { | ||||
|   final LocalChatMessage message; | ||||
|   final bool isCurrentUser; | ||||
|   final Map<int, double>? progress; | ||||
|   final bool showAvatar; | ||||
|   final Function(String messageId) onJump; | ||||
|   final String? translatedText; | ||||
|   final bool translating; | ||||
|  | ||||
|   const MessageItemDisplayBubble({ | ||||
|     super.key, | ||||
|     required this.message, | ||||
|     required this.isCurrentUser, | ||||
|     required this.progress, | ||||
|     required this.showAvatar, | ||||
|     required this.onJump, | ||||
|     required this.translatedText, | ||||
|     required this.translating, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final textColor = | ||||
| @@ -108,111 +294,7 @@ class MessageItem extends HookConsumerWidget { | ||||
|     final remoteMessage = message.toRemoteMessage(); | ||||
|     final sender = remoteMessage.sender; | ||||
|  | ||||
|     final isMobile = !kIsWeb && (Platform.isAndroid || Platform.isIOS); | ||||
|  | ||||
|     final messageLanguage = | ||||
|         remoteMessage.content != null | ||||
|             ? ref.watch(detectStringLanguageProvider(remoteMessage.content!)) | ||||
|             : null; | ||||
|  | ||||
|     final currentLanguage = context.locale.toString(); | ||||
|     final translatableLanguage = | ||||
|         messageLanguage != null | ||||
|             ? messageLanguage.substring(0, 2) != currentLanguage.substring(0, 2) | ||||
|             : false; | ||||
|  | ||||
|     final translating = useState(false); | ||||
|     final translatedText = useState<String?>(null); | ||||
|  | ||||
|     Future<void> translate() async { | ||||
|       if (translatedText.value != null) { | ||||
|         translatedText.value = null; | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       if (translating.value) return; | ||||
|       if (remoteMessage.content == null) return; | ||||
|       translating.value = true; | ||||
|       try { | ||||
|         final text = await ref.watch( | ||||
|           translateStringProvider( | ||||
|             TranslateQuery( | ||||
|               text: remoteMessage.content!, | ||||
|               lang: currentLanguage.substring(0, 2), | ||||
|             ), | ||||
|           ).future, | ||||
|         ); | ||||
|         translatedText.value = text; | ||||
|       } catch (err) { | ||||
|         showErrorAlert(err); | ||||
|       } finally { | ||||
|         translating.value = false; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return ContextMenuWidget( | ||||
|       menuProvider: (_) { | ||||
|         if (onAction == null) return Menu(children: []); | ||||
|         return Menu( | ||||
|           children: [ | ||||
|             if (isCurrentUser) | ||||
|               MenuAction( | ||||
|                 title: 'edit'.tr(), | ||||
|                 image: MenuImage.icon(Symbols.edit), | ||||
|                 callback: () { | ||||
|                   onAction!.call(MessageItemAction.edit); | ||||
|                 }, | ||||
|               ), | ||||
|             if (isCurrentUser) | ||||
|               MenuAction( | ||||
|                 title: 'delete'.tr(), | ||||
|                 image: MenuImage.icon(Symbols.delete), | ||||
|                 callback: () { | ||||
|                   onAction!.call(MessageItemAction.delete); | ||||
|                 }, | ||||
|               ), | ||||
|             if (isCurrentUser) MenuSeparator(), | ||||
|             MenuAction( | ||||
|               title: 'reply'.tr(), | ||||
|               image: MenuImage.icon(Symbols.reply), | ||||
|               callback: () { | ||||
|                 onAction!.call(MessageItemAction.reply); | ||||
|               }, | ||||
|             ), | ||||
|             MenuAction( | ||||
|               title: 'forward'.tr(), | ||||
|               image: MenuImage.icon(Symbols.forward), | ||||
|               callback: () { | ||||
|                 onAction!.call(MessageItemAction.forward); | ||||
|               }, | ||||
|             ), | ||||
|             if (translatableLanguage) MenuSeparator(), | ||||
|             if (translatableLanguage) | ||||
|               MenuAction( | ||||
|                 title: | ||||
|                     translatedText.value == null | ||||
|                         ? 'translate'.tr() | ||||
|                         : translating.value | ||||
|                         ? 'translating'.tr() | ||||
|                         : 'translated'.tr(), | ||||
|                 image: MenuImage.icon(Symbols.translate), | ||||
|                 callback: translate, | ||||
|               ), | ||||
|             if (isMobile) MenuSeparator(), | ||||
|             if (isMobile) | ||||
|               MenuAction( | ||||
|                 title: 'copyMessage'.tr(), | ||||
|                 image: MenuImage.icon(Symbols.copy_all), | ||||
|                 callback: () { | ||||
|                   Clipboard.setData( | ||||
|                     ClipboardData(text: remoteMessage.content ?? ''), | ||||
|                   ); | ||||
|                 }, | ||||
|               ), | ||||
|           ], | ||||
|         ); | ||||
|       }, | ||||
|       child: Material( | ||||
|     return Material( | ||||
|       color: | ||||
|           hasBackground | ||||
|               ? Colors.transparent | ||||
| @@ -267,7 +349,7 @@ class MessageItem extends HookConsumerWidget { | ||||
|                         if (MessageContent.hasContent(remoteMessage)) | ||||
|                           MessageContent( | ||||
|                             item: remoteMessage, | ||||
|                               translatedText: translatedText.value, | ||||
|                             translatedText: translatedText, | ||||
|                           ), | ||||
|                         if (remoteMessage.attachments.isNotEmpty) | ||||
|                           LayoutBuilder( | ||||
| @@ -281,9 +363,7 @@ class MessageItem extends HookConsumerWidget { | ||||
|                           ), | ||||
|                         if (remoteMessage.meta['embeds'] != null) | ||||
|                           ...((remoteMessage.meta['embeds'] as List<dynamic>) | ||||
|                                 .map( | ||||
|                                   (embed) => convertMapKeysToSnakeCase(embed), | ||||
|                                 ) | ||||
|                               .map((embed) => convertMapKeysToSnakeCase(embed)) | ||||
|                               .where((embed) => embed['type'] == 'link') | ||||
|                               .map((embed) => SnScrappedLink.fromJson(embed)) | ||||
|                               .map( | ||||
| @@ -308,13 +388,11 @@ class MessageItem extends HookConsumerWidget { | ||||
|                             crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                             spacing: 8, | ||||
|                             children: [ | ||||
|                                 if ((remoteMessage.content?.isNotEmpty ?? | ||||
|                                     false)) | ||||
|                               if ((remoteMessage.content?.isNotEmpty ?? false)) | ||||
|                                 const Gap(0), | ||||
|                               for (var entry in progress!.entries) | ||||
|                                 Column( | ||||
|                                     crossAxisAlignment: | ||||
|                                         CrossAxisAlignment.start, | ||||
|                                   crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                                   children: [ | ||||
|                                     Text( | ||||
|                                       'fileUploadingProgress'.tr( | ||||
| @@ -335,11 +413,8 @@ class MessageItem extends HookConsumerWidget { | ||||
|                                           Theme.of( | ||||
|                                             context, | ||||
|                                           ).colorScheme.surfaceVariant, | ||||
|                                         valueColor: | ||||
|                                             AlwaysStoppedAnimation<Color>( | ||||
|                                               Theme.of( | ||||
|                                                 context, | ||||
|                                               ).colorScheme.primary, | ||||
|                                       valueColor: AlwaysStoppedAnimation<Color>( | ||||
|                                         Theme.of(context).colorScheme.primary, | ||||
|                                       ), | ||||
|                                     ), | ||||
|                                   ], | ||||
| @@ -362,6 +437,311 @@ class MessageItem extends HookConsumerWidget { | ||||
|           ], | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class MessageItemDisplayIRC extends HookConsumerWidget { | ||||
|   final LocalChatMessage message; | ||||
|   final bool isCurrentUser; | ||||
|   final Map<int, double>? progress; | ||||
|   final bool showAvatar; | ||||
|   final Function(String messageId) onJump; | ||||
|   final String? translatedText; | ||||
|   final bool translating; | ||||
|  | ||||
|   const MessageItemDisplayIRC({ | ||||
|     super.key, | ||||
|     required this.message, | ||||
|     required this.isCurrentUser, | ||||
|     required this.progress, | ||||
|     required this.showAvatar, | ||||
|     required this.onJump, | ||||
|     required this.translatedText, | ||||
|     required this.translating, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final remoteMessage = message.toRemoteMessage(); | ||||
|     final sender = remoteMessage.sender; | ||||
|     final textColor = Theme.of(context).colorScheme.onSurfaceVariant; | ||||
|  | ||||
|     return Padding( | ||||
|       padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2), | ||||
|       child: Row( | ||||
|         children: [ | ||||
|           Text( | ||||
|             DateFormat('HH:mm').format(message.createdAt), | ||||
|             style: TextStyle(color: textColor.withOpacity(0.7), fontSize: 12), | ||||
|           ), | ||||
|           const SizedBox(width: 8), | ||||
|           Text( | ||||
|             '<${sender.account.nick}>', | ||||
|             style: TextStyle(color: Colors.blue), | ||||
|           ), | ||||
|           const SizedBox(width: 8), | ||||
|           Expanded( | ||||
|             child: Text( | ||||
|               translatedText ?? remoteMessage.content ?? '', | ||||
|               style: TextStyle(color: textColor), | ||||
|             ), | ||||
|           ), | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class MessageItemDisplayDiscord extends HookConsumerWidget { | ||||
|   final LocalChatMessage message; | ||||
|   final bool isCurrentUser; | ||||
|   final Map<int, double>? progress; | ||||
|   final bool showAvatar; | ||||
|   final Function(String messageId) onJump; | ||||
|   final String? translatedText; | ||||
|   final bool translating; | ||||
|  | ||||
|   const MessageItemDisplayDiscord({ | ||||
|     super.key, | ||||
|     required this.message, | ||||
|     required this.isCurrentUser, | ||||
|     required this.progress, | ||||
|     required this.showAvatar, | ||||
|     required this.onJump, | ||||
|     required this.translatedText, | ||||
|     required this.translating, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final textColor = Theme.of(context).colorScheme.onSurfaceVariant; | ||||
|     final remoteMessage = message.toRemoteMessage(); | ||||
|     final sender = remoteMessage.sender; | ||||
|  | ||||
|     const kAvatarRadius = 12.0; | ||||
|  | ||||
|     return Padding( | ||||
|       padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), | ||||
|       child: | ||||
|           showAvatar | ||||
|               ? Column( | ||||
|                 crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                 children: [ | ||||
|                   Row( | ||||
|                     spacing: 8, | ||||
|                     children: [ | ||||
|                       AccountPfcGestureDetector( | ||||
|                         uname: sender.account.name, | ||||
|                         child: ProfilePictureWidget( | ||||
|                           file: sender.account.profile.picture, | ||||
|                           radius: kAvatarRadius, | ||||
|                         ), | ||||
|                       ), | ||||
|                       MessageSenderInfo( | ||||
|                         sender: sender, | ||||
|                         createdAt: message.createdAt, | ||||
|                         textColor: textColor, | ||||
|                         showAvatar: false, | ||||
|                         isCompact: true, | ||||
|                       ), | ||||
|                     ], | ||||
|                   ), | ||||
|                   Column( | ||||
|                     crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                     children: [ | ||||
|                       if (remoteMessage.repliedMessageId != null) | ||||
|                         MessageQuoteWidget( | ||||
|                           message: message, | ||||
|                           textColor: textColor, | ||||
|                           isReply: true, | ||||
|                         ).padding(vertical: 4), | ||||
|                       if (remoteMessage.forwardedMessageId != null) | ||||
|                         MessageQuoteWidget( | ||||
|                           message: message, | ||||
|                           textColor: textColor, | ||||
|                           isReply: false, | ||||
|                         ).padding(vertical: 4), | ||||
|                       if (MessageContent.hasContent(remoteMessage)) | ||||
|                         MessageContent( | ||||
|                           item: remoteMessage, | ||||
|                           translatedText: translatedText, | ||||
|                         ), | ||||
|                       if (remoteMessage.attachments.isNotEmpty) | ||||
|                         LayoutBuilder( | ||||
|                           builder: (context, constraints) { | ||||
|                             return CloudFileList( | ||||
|                               files: remoteMessage.attachments, | ||||
|                               maxWidth: constraints.maxWidth, | ||||
|                               padding: EdgeInsets.symmetric(vertical: 4), | ||||
|                             ); | ||||
|                           }, | ||||
|                         ), | ||||
|                       if (remoteMessage.meta['embeds'] != null) | ||||
|                         ...((remoteMessage.meta['embeds'] as List<dynamic>) | ||||
|                             .map((embed) => convertMapKeysToSnakeCase(embed)) | ||||
|                             .where((embed) => embed['type'] == 'link') | ||||
|                             .map((embed) => SnScrappedLink.fromJson(embed)) | ||||
|                             .map( | ||||
|                               (link) => LayoutBuilder( | ||||
|                                 builder: (context, constraints) { | ||||
|                                   return EmbedLinkWidget( | ||||
|                                     link: link, | ||||
|                                     maxWidth: math.min( | ||||
|                                       constraints.maxWidth, | ||||
|                                       480, | ||||
|                                     ), | ||||
|                                     margin: const EdgeInsets.symmetric( | ||||
|                                       vertical: 4, | ||||
|                                     ), | ||||
|                                   ); | ||||
|                                 }, | ||||
|                               ), | ||||
|                             ) | ||||
|                             .toList()), | ||||
|                       if (progress != null && progress!.isNotEmpty) | ||||
|                         Column( | ||||
|                           crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                           spacing: 8, | ||||
|                           children: [ | ||||
|                             if ((remoteMessage.content?.isNotEmpty ?? false)) | ||||
|                               const SizedBox.shrink(), | ||||
|                             for (var entry in progress!.entries) | ||||
|                               Column( | ||||
|                                 crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                                 children: [ | ||||
|                                   Text( | ||||
|                                     'fileUploadingProgress'.tr( | ||||
|                                       args: [ | ||||
|                                         (entry.key + 1).toString(), | ||||
|                                         entry.value.toStringAsFixed(1), | ||||
|                                       ], | ||||
|                                     ), | ||||
|                                     style: TextStyle( | ||||
|                                       fontSize: 12, | ||||
|                                       color: textColor.withOpacity(0.8), | ||||
|                                     ), | ||||
|                                   ), | ||||
|                                   const Gap(4), | ||||
|                                   LinearProgressIndicator( | ||||
|                                     value: entry.value / 100, | ||||
|                                     backgroundColor: | ||||
|                                         Theme.of( | ||||
|                                           context, | ||||
|                                         ).colorScheme.surfaceVariant, | ||||
|                                     valueColor: AlwaysStoppedAnimation<Color>( | ||||
|                                       Theme.of(context).colorScheme.primary, | ||||
|                                     ), | ||||
|                                   ), | ||||
|                                 ], | ||||
|                               ), | ||||
|                             const Gap(0), | ||||
|                           ], | ||||
|                         ), | ||||
|                     ], | ||||
|                   ).padding(left: kAvatarRadius * 2 + 8), | ||||
|                 ], | ||||
|               ) | ||||
|               : Padding( | ||||
|                 padding: EdgeInsets.only(left: kAvatarRadius * 2 + 8), | ||||
|                 child: Column( | ||||
|                   crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                   children: [ | ||||
|                     if (showAvatar) | ||||
|                       MessageSenderInfo( | ||||
|                         sender: sender, | ||||
|                         createdAt: message.createdAt, | ||||
|                         textColor: textColor, | ||||
|                         showAvatar: false, | ||||
|                         isCompact: true, | ||||
|                       ), | ||||
|                     if (remoteMessage.repliedMessageId != null) | ||||
|                       MessageQuoteWidget( | ||||
|                         message: message, | ||||
|                         textColor: textColor, | ||||
|                         isReply: true, | ||||
|                       ).padding(vertical: 4), | ||||
|                     if (remoteMessage.forwardedMessageId != null) | ||||
|                       MessageQuoteWidget( | ||||
|                         message: message, | ||||
|                         textColor: textColor, | ||||
|                         isReply: false, | ||||
|                       ).padding(vertical: 4), | ||||
|                     if (MessageContent.hasContent(remoteMessage)) | ||||
|                       MessageContent( | ||||
|                         item: remoteMessage, | ||||
|                         translatedText: translatedText, | ||||
|                       ), | ||||
|                     if (remoteMessage.attachments.isNotEmpty) | ||||
|                       LayoutBuilder( | ||||
|                         builder: (context, constraints) { | ||||
|                           return CloudFileList( | ||||
|                             files: remoteMessage.attachments, | ||||
|                             maxWidth: constraints.maxWidth, | ||||
|                             padding: EdgeInsets.symmetric(vertical: 4), | ||||
|                           ); | ||||
|                         }, | ||||
|                       ), | ||||
|                     if (remoteMessage.meta['embeds'] != null) | ||||
|                       ...((remoteMessage.meta['embeds'] as List<dynamic>) | ||||
|                           .map((embed) => convertMapKeysToSnakeCase(embed)) | ||||
|                           .where((embed) => embed['type'] == 'link') | ||||
|                           .map((embed) => SnScrappedLink.fromJson(embed)) | ||||
|                           .map( | ||||
|                             (link) => LayoutBuilder( | ||||
|                               builder: (context, constraints) { | ||||
|                                 return EmbedLinkWidget( | ||||
|                                   link: link, | ||||
|                                   maxWidth: math.min(constraints.maxWidth, 480), | ||||
|                                   margin: const EdgeInsets.symmetric( | ||||
|                                     vertical: 4, | ||||
|                                   ), | ||||
|                                 ); | ||||
|                               }, | ||||
|                             ), | ||||
|                           ) | ||||
|                           .toList()), | ||||
|                     if (progress != null && progress!.isNotEmpty) | ||||
|                       Column( | ||||
|                         crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                         spacing: 8, | ||||
|                         children: [ | ||||
|                           if ((remoteMessage.content?.isNotEmpty ?? false)) | ||||
|                             const Gap(0), | ||||
|                           for (var entry in progress!.entries) | ||||
|                             Column( | ||||
|                               crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                               children: [ | ||||
|                                 Text( | ||||
|                                   'fileUploadingProgress'.tr( | ||||
|                                     args: [ | ||||
|                                       (entry.key + 1).toString(), | ||||
|                                       entry.value.toStringAsFixed(1), | ||||
|                                     ], | ||||
|                                   ), | ||||
|                                   style: TextStyle( | ||||
|                                     fontSize: 12, | ||||
|                                     color: textColor.withOpacity(0.8), | ||||
|                                   ), | ||||
|                                 ), | ||||
|                                 const Gap(4), | ||||
|                                 LinearProgressIndicator( | ||||
|                                   value: entry.value / 100, | ||||
|                                   backgroundColor: | ||||
|                                       Theme.of( | ||||
|                                         context, | ||||
|                                       ).colorScheme.surfaceVariant, | ||||
|                                   valueColor: AlwaysStoppedAnimation<Color>( | ||||
|                                     Theme.of(context).colorScheme.primary, | ||||
|                                   ), | ||||
|                                 ), | ||||
|                               ], | ||||
|                             ), | ||||
|                           const Gap(0), | ||||
|                         ], | ||||
|                       ), | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|     ); | ||||
|   } | ||||
|   | ||||
| @@ -41,7 +41,7 @@ class MessageListTile extends StatelessWidget { | ||||
|             sender: sender, | ||||
|             createdAt: message.createdAt, | ||||
|             textColor: Theme.of(context).colorScheme.onSurfaceVariant, | ||||
|             compact: true, | ||||
|             showAvatar: false, | ||||
|           ), | ||||
|           const SizedBox(height: 4), | ||||
|           MessageContent(item: remoteMessage), | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:gap/gap.dart'; | ||||
| import 'package:island/models/chat.dart'; | ||||
| import 'package:island/widgets/account/account_name.dart'; | ||||
| import 'package:island/widgets/account/account_pfc.dart'; | ||||
| @@ -9,14 +10,16 @@ class MessageSenderInfo extends StatelessWidget { | ||||
|   final SnChatMember sender; | ||||
|   final DateTime createdAt; | ||||
|   final Color textColor; | ||||
|   final bool compact; | ||||
|   final bool showAvatar; | ||||
|   final bool isCompact; | ||||
|  | ||||
|   const MessageSenderInfo({ | ||||
|     super.key, | ||||
|     required this.sender, | ||||
|     required this.createdAt, | ||||
|     required this.textColor, | ||||
|     this.compact = false, | ||||
|     this.showAvatar = true, | ||||
|     this.isCompact = false, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
| @@ -28,11 +31,41 @@ class MessageSenderInfo extends StatelessWidget { | ||||
|             ? DateFormat('MM/dd HH:mm').format(createdAt.toLocal()) | ||||
|             : DateFormat('HH:mm').format(createdAt.toLocal()); | ||||
|  | ||||
|     if (compact) { | ||||
|     if (isCompact) { | ||||
|       return Row( | ||||
|         mainAxisSize: MainAxisSize.min, | ||||
|         crossAxisAlignment: CrossAxisAlignment.baseline, | ||||
|         textBaseline: TextBaseline.alphabetic, | ||||
|         children: [ | ||||
|           if (showAvatar) | ||||
|             AccountPfcGestureDetector( | ||||
|               uname: sender.account.name, | ||||
|               child: ProfilePictureWidget( | ||||
|                 fileId: sender.account.profile.picture?.id, | ||||
|                 radius: 14, | ||||
|               ), | ||||
|             ), | ||||
|           if (showAvatar) const Gap(4), | ||||
|           AccountName( | ||||
|             account: sender.account, | ||||
|             style: Theme.of(context).textTheme.bodySmall?.copyWith( | ||||
|               color: textColor, | ||||
|               fontWeight: FontWeight.w500, | ||||
|             ), | ||||
|           ), | ||||
|           const Gap(6), | ||||
|           Text( | ||||
|             timestamp, | ||||
|             style: TextStyle(fontSize: 10, color: textColor.withOpacity(0.7)), | ||||
|           ), | ||||
|         ], | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     if (showAvatar) { | ||||
|       return Row( | ||||
|         spacing: 8, | ||||
|         children: [ | ||||
|           if (!compact) | ||||
|           AccountPfcGestureDetector( | ||||
|             uname: sender.account.name, | ||||
|             child: ProfilePictureWidget( | ||||
| @@ -84,6 +117,7 @@ class MessageSenderInfo extends StatelessWidget { | ||||
|       spacing: 8, | ||||
|       mainAxisSize: MainAxisSize.min, | ||||
|       children: [ | ||||
|         if (showAvatar) | ||||
|           AccountPfcGestureDetector( | ||||
|             uname: sender.account.name, | ||||
|             child: ProfilePictureWidget( | ||||
|   | ||||
		Reference in New Issue
	
	Block a user