Merge pull request 'develop-replyToMessages' (#67) from develop-replyToMessages into develop
Reviewed-on: #67 Reviewed-by: Pupsi <larslukasneuhaus@gmx.de>
This commit is contained in:
commit
430f2fe603
@ -38,6 +38,7 @@ class GetChatResponseObject {
|
|||||||
Map<String, int>? reactions;
|
Map<String, int>? reactions;
|
||||||
List<String>? reactionsSelf;
|
List<String>? reactionsSelf;
|
||||||
@JsonKey(fromJson: _fromJson) Map<String, RichObjectString>? messageParameters;
|
@JsonKey(fromJson: _fromJson) Map<String, RichObjectString>? messageParameters;
|
||||||
|
GetChatResponseObject? parent;
|
||||||
|
|
||||||
GetChatResponseObject(
|
GetChatResponseObject(
|
||||||
this.id,
|
this.id,
|
||||||
@ -53,7 +54,8 @@ class GetChatResponseObject {
|
|||||||
this.message,
|
this.message,
|
||||||
this.messageParameters,
|
this.messageParameters,
|
||||||
this.reactions,
|
this.reactions,
|
||||||
this.reactionsSelf
|
this.reactionsSelf,
|
||||||
|
this.parent,
|
||||||
);
|
);
|
||||||
|
|
||||||
factory GetChatResponseObject.fromJson(Map<String, dynamic> json) => _$GetChatResponseObjectFromJson(json);
|
factory GetChatResponseObject.fromJson(Map<String, dynamic> json) => _$GetChatResponseObjectFromJson(json);
|
||||||
@ -78,7 +80,8 @@ class GetChatResponseObject {
|
|||||||
text,
|
text,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null
|
null,
|
||||||
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,10 @@ GetChatResponseObject _$GetChatResponseObjectFromJson(
|
|||||||
(json['reactionsSelf'] as List<dynamic>?)
|
(json['reactionsSelf'] as List<dynamic>?)
|
||||||
?.map((e) => e as String)
|
?.map((e) => e as String)
|
||||||
.toList(),
|
.toList(),
|
||||||
|
json['parent'] == null
|
||||||
|
? null
|
||||||
|
: GetChatResponseObject.fromJson(
|
||||||
|
json['parent'] as Map<String, dynamic>),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$GetChatResponseObjectToJson(
|
Map<String, dynamic> _$GetChatResponseObjectToJson(
|
||||||
@ -74,6 +78,7 @@ Map<String, dynamic> _$GetChatResponseObjectToJson(
|
|||||||
'reactionsSelf': instance.reactionsSelf,
|
'reactionsSelf': instance.reactionsSelf,
|
||||||
'messageParameters':
|
'messageParameters':
|
||||||
instance.messageParameters?.map((k, e) => MapEntry(k, e.toJson())),
|
instance.messageParameters?.map((k, e) => MapEntry(k, e.toJson())),
|
||||||
|
'parent': instance.parent?.toJson(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const _$GetRoomResponseObjectMessageActorTypeEnumMap = {
|
const _$GetRoomResponseObjectMessageActorTypeEnumMap = {
|
||||||
|
@ -7,7 +7,7 @@ part 'sendMessageParams.g.dart';
|
|||||||
@JsonSerializable(explicitToJson: true, includeIfNull: false)
|
@JsonSerializable(explicitToJson: true, includeIfNull: false)
|
||||||
class SendMessageParams extends ApiParams {
|
class SendMessageParams extends ApiParams {
|
||||||
String message;
|
String message;
|
||||||
int? replyTo;
|
String? replyTo;
|
||||||
|
|
||||||
SendMessageParams(this.message, {this.replyTo});
|
SendMessageParams(this.message, {this.replyTo});
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ part of 'sendMessageParams.dart';
|
|||||||
SendMessageParams _$SendMessageParamsFromJson(Map<String, dynamic> json) =>
|
SendMessageParams _$SendMessageParamsFromJson(Map<String, dynamic> json) =>
|
||||||
SendMessageParams(
|
SendMessageParams(
|
||||||
json['message'] as String,
|
json['message'] as String,
|
||||||
replyTo: json['replyTo'] as int?,
|
replyTo: json['replyTo'] as String?,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$SendMessageParamsToJson(SendMessageParams instance) {
|
Map<String, dynamic> _$SendMessageParamsToJson(SendMessageParams instance) {
|
||||||
|
@ -11,6 +11,7 @@ import 'package:badges/badges.dart' as badges;
|
|||||||
|
|
||||||
import 'api/mhsl/breaker/getBreakers/getBreakersResponse.dart';
|
import 'api/mhsl/breaker/getBreakers/getBreakersResponse.dart';
|
||||||
import 'api/mhsl/server/userIndex/update/updateUserindex.dart';
|
import 'api/mhsl/server/userIndex/update/updateUserindex.dart';
|
||||||
|
import 'main.dart';
|
||||||
import 'model/breakers/Breaker.dart';
|
import 'model/breakers/Breaker.dart';
|
||||||
import 'model/breakers/BreakerProps.dart';
|
import 'model/breakers/BreakerProps.dart';
|
||||||
import 'model/chatList/chatListProps.dart';
|
import 'model/chatList/chatListProps.dart';
|
||||||
@ -28,13 +29,12 @@ import 'view/pages/timetable/timetable.dart';
|
|||||||
class App extends StatefulWidget {
|
class App extends StatefulWidget {
|
||||||
const App({super.key});
|
const App({super.key});
|
||||||
|
|
||||||
static PersistentTabController bottomNavigator = PersistentTabController(initialIndex: 0);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<App> createState() => _AppState();
|
State<App> createState() => _AppState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppState extends State<App> with WidgetsBindingObserver {
|
class _AppState extends State<App> with WidgetsBindingObserver {
|
||||||
|
|
||||||
late Timer refetchChats;
|
late Timer refetchChats;
|
||||||
late Timer updateTimings;
|
late Timer updateTimings;
|
||||||
|
|
||||||
@ -58,6 +58,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
Main.bottomNavigator = PersistentTabController(initialIndex: 0);
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||||
Provider.of<BreakerProps>(context, listen: false).run();
|
Provider.of<BreakerProps>(context, listen: false).run();
|
||||||
@ -95,7 +96,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => PersistentTabView(
|
Widget build(BuildContext context) => PersistentTabView(
|
||||||
controller: App.bottomNavigator,
|
controller: Main.bottomNavigator,
|
||||||
navBarOverlap: const NavBarOverlap.none(),
|
navBarOverlap: const NavBarOverlap.none(),
|
||||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:jiffy/jiffy.dart';
|
import 'package:jiffy/jiffy.dart';
|
||||||
import 'package:loader_overlay/loader_overlay.dart';
|
import 'package:loader_overlay/loader_overlay.dart';
|
||||||
|
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
|
||||||
@ -74,6 +75,8 @@ Future<void> main() async {
|
|||||||
|
|
||||||
class Main extends StatefulWidget {
|
class Main extends StatefulWidget {
|
||||||
const Main({super.key});
|
const Main({super.key});
|
||||||
|
static PersistentTabController bottomNavigator = PersistentTabController(initialIndex: 0);
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<Main> createState() => _MainState();
|
State<Main> createState() => _MainState();
|
||||||
|
@ -1,15 +1,23 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../../api/apiResponse.dart';
|
import '../../api/apiResponse.dart';
|
||||||
import '../../api/marianumcloud/talk/chat/getChatCache.dart';
|
import '../../api/marianumcloud/talk/chat/getChatCache.dart';
|
||||||
import '../../api/marianumcloud/talk/chat/getChatResponse.dart';
|
import '../../api/marianumcloud/talk/chat/getChatResponse.dart';
|
||||||
|
import '../../storage/base/settingsProvider.dart';
|
||||||
import '../dataHolder.dart';
|
import '../dataHolder.dart';
|
||||||
|
|
||||||
class ChatProps extends DataHolder {
|
class ChatProps extends DataHolder {
|
||||||
String _queryToken = '';
|
String _queryToken = '';
|
||||||
DateTime _lastTokenSet = DateTime.now();
|
DateTime _lastTokenSet = DateTime.now();
|
||||||
|
int? _referenceMessageId;
|
||||||
|
|
||||||
GetChatResponse? _getChatResponse;
|
GetChatResponse? _getChatResponse;
|
||||||
GetChatResponse get getChatResponse => _getChatResponse!;
|
GetChatResponse get getChatResponse => _getChatResponse!;
|
||||||
|
|
||||||
|
int? get getReferenceMessageId => _referenceMessageId;
|
||||||
|
set unsafeInternalSetReferenceMessageId(int? reference) => _referenceMessageId = reference;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<ApiResponse?> properties() => [_getChatResponse];
|
List<ApiResponse?> properties() => [_getChatResponse];
|
||||||
|
|
||||||
@ -30,6 +38,20 @@ class ChatProps extends DataHolder {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setReferenceMessageId(int? messageId, BuildContext context, String sendToToken) {
|
||||||
|
Future.microtask(() {
|
||||||
|
_referenceMessageId = messageId;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
var settings = Provider.of<SettingsProvider>(context, listen: false);
|
||||||
|
if(messageId != null) {
|
||||||
|
settings.val(write: true).talkSettings.draftReplies[sendToToken] = messageId;
|
||||||
|
} else {
|
||||||
|
settings.val(write: true).talkSettings.draftReplies.removeWhere((key, value) => key == sendToToken);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void setQueryToken(String token) {
|
void setQueryToken(String token) {
|
||||||
_queryToken = token;
|
_queryToken = token;
|
||||||
_getChatResponse = null;
|
_getChatResponse = null;
|
||||||
|
@ -3,7 +3,7 @@ import 'package:flutter/cupertino.dart';
|
|||||||
import 'package:flutter_app_badger/flutter_app_badger.dart';
|
import 'package:flutter_app_badger/flutter_app_badger.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../app.dart';
|
import '../main.dart';
|
||||||
import '../model/chatList/chatListProps.dart';
|
import '../model/chatList/chatListProps.dart';
|
||||||
import '../model/chatList/chatProps.dart';
|
import '../model/chatList/chatProps.dart';
|
||||||
|
|
||||||
@ -18,6 +18,6 @@ class NotificationTasks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void navigateToTalk() {
|
static void navigateToTalk() {
|
||||||
App.bottomNavigator.jumpToTab(1);
|
Main.bottomNavigator.jumpToTab(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,9 @@ class TalkSettings {
|
|||||||
bool sortFavoritesToTop;
|
bool sortFavoritesToTop;
|
||||||
bool sortUnreadToTop;
|
bool sortUnreadToTop;
|
||||||
Map<String, String> drafts;
|
Map<String, String> drafts;
|
||||||
|
Map<String, int> draftReplies;
|
||||||
|
|
||||||
TalkSettings({required this.sortFavoritesToTop, required this.sortUnreadToTop, required this.drafts});
|
TalkSettings({required this.sortFavoritesToTop, required this.sortUnreadToTop, required this.drafts, required this.draftReplies});
|
||||||
|
|
||||||
factory TalkSettings.fromJson(Map<String, dynamic> json) => _$TalkSettingsFromJson(json);
|
factory TalkSettings.fromJson(Map<String, dynamic> json) => _$TalkSettingsFromJson(json);
|
||||||
Map<String, dynamic> toJson() => _$TalkSettingsToJson(this);
|
Map<String, dynamic> toJson() => _$TalkSettingsToJson(this);
|
||||||
|
@ -10,6 +10,7 @@ TalkSettings _$TalkSettingsFromJson(Map<String, dynamic> json) => TalkSettings(
|
|||||||
sortFavoritesToTop: json['sortFavoritesToTop'] as bool,
|
sortFavoritesToTop: json['sortFavoritesToTop'] as bool,
|
||||||
sortUnreadToTop: json['sortUnreadToTop'] as bool,
|
sortUnreadToTop: json['sortUnreadToTop'] as bool,
|
||||||
drafts: Map<String, String>.from(json['drafts'] as Map),
|
drafts: Map<String, String>.from(json['drafts'] as Map),
|
||||||
|
draftReplies: Map<String, int>.from(json['draftReplies'] as Map),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$TalkSettingsToJson(TalkSettings instance) =>
|
Map<String, dynamic> _$TalkSettingsToJson(TalkSettings instance) =>
|
||||||
@ -17,4 +18,5 @@ Map<String, dynamic> _$TalkSettingsToJson(TalkSettings instance) =>
|
|||||||
'sortFavoritesToTop': instance.sortFavoritesToTop,
|
'sortFavoritesToTop': instance.sortFavoritesToTop,
|
||||||
'sortUnreadToTop': instance.sortUnreadToTop,
|
'sortUnreadToTop': instance.sortUnreadToTop,
|
||||||
'drafts': instance.drafts,
|
'drafts': instance.drafts,
|
||||||
|
'draftReplies': instance.draftReplies,
|
||||||
};
|
};
|
||||||
|
@ -71,6 +71,7 @@ class _ChatViewState extends State<ChatView> {
|
|||||||
chatData: widget.room,
|
chatData: widget.room,
|
||||||
refetch: _query,
|
refetch: _query,
|
||||||
isRead: element.id <= commonRead,
|
isRead: element.id <= commonRead,
|
||||||
|
selfId: widget.selfId,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -129,8 +130,8 @@ class _ChatViewState extends State<ChatView> {
|
|||||||
Container(
|
Container(
|
||||||
color: Theme.of(context).colorScheme.background,
|
color: Theme.of(context).colorScheme.background,
|
||||||
child: TalkNavigator.isSecondaryVisible(context)
|
child: TalkNavigator.isSecondaryVisible(context)
|
||||||
? ChatTextfield(widget.room.token)
|
? ChatTextfield(widget.room.token, selfId: widget.selfId)
|
||||||
: SafeArea(child: ChatTextfield(widget.room.token)
|
: SafeArea(child: ChatTextfield(widget.room.token, selfId: widget.selfId)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
59
lib/view/pages/talk/components/answerReference.dart
Normal file
59
lib/view/pages/talk/components/answerReference.dart
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../../../api/marianumcloud/talk/chat/getChatResponse.dart';
|
||||||
|
import '../../../../api/marianumcloud/talk/chat/richObjectStringProcessor.dart';
|
||||||
|
import 'chatBubbleStyles.dart';
|
||||||
|
|
||||||
|
class AnswerReference extends StatelessWidget {
|
||||||
|
final BuildContext context;
|
||||||
|
final GetChatResponseObject referenceMessage;
|
||||||
|
final String? selfId;
|
||||||
|
const AnswerReference({required this.context, required this.referenceMessage, required this.selfId, super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var style = ChatBubbleStyles(context);
|
||||||
|
return DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: referenceMessage.actorId == selfId
|
||||||
|
? style.getSelfStyle(false).color!.withGreen(200).withOpacity(0.2)
|
||||||
|
: style.getRemoteStyle(false).color!.withWhite(200).withOpacity(0.2),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(5)),
|
||||||
|
border: Border(left: BorderSide(
|
||||||
|
color: referenceMessage.actorId == selfId
|
||||||
|
? style.getSelfStyle(false).color!.withGreen(200)
|
||||||
|
: style.getRemoteStyle(false).color!.withWhite(200),
|
||||||
|
width: 5
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(5).add(const EdgeInsets.only(left: 5)),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
referenceMessage.actorDisplayName,
|
||||||
|
maxLines: 1,
|
||||||
|
style: TextStyle(
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
color: referenceMessage.actorId == selfId
|
||||||
|
? style.getSelfStyle(false).color!.withGreen(200)
|
||||||
|
: style.getRemoteStyle(false).color!.withWhite(200),
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
RichObjectStringProcessor.parseToString(referenceMessage.message, referenceMessage.messageParameters),
|
||||||
|
maxLines: 2,
|
||||||
|
style: TextStyle(
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ import 'package:better_open_file/better_open_file.dart';
|
|||||||
import 'package:bubble/bubble.dart';
|
import 'package:bubble/bubble.dart';
|
||||||
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart' as emojis;
|
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart' as emojis;
|
||||||
import 'package:flowder/flowder.dart';
|
import 'package:flowder/flowder.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@ -17,9 +18,10 @@ import '../../../../api/marianumcloud/talk/reactMessage/reactMessage.dart';
|
|||||||
import '../../../../api/marianumcloud/talk/reactMessage/reactMessageParams.dart';
|
import '../../../../api/marianumcloud/talk/reactMessage/reactMessageParams.dart';
|
||||||
import '../../../../api/marianumcloud/talk/room/getRoomResponse.dart';
|
import '../../../../api/marianumcloud/talk/room/getRoomResponse.dart';
|
||||||
import '../../../../model/chatList/chatProps.dart';
|
import '../../../../model/chatList/chatProps.dart';
|
||||||
import '../../../../theming/appTheme.dart';
|
|
||||||
import '../../../../widget/debug/debugTile.dart';
|
import '../../../../widget/debug/debugTile.dart';
|
||||||
import '../../files/fileElement.dart';
|
import '../../files/fileElement.dart';
|
||||||
|
import 'answerReference.dart';
|
||||||
|
import 'chatBubbleStyles.dart';
|
||||||
import 'chatMessage.dart';
|
import 'chatMessage.dart';
|
||||||
import '../messageReactions.dart';
|
import '../messageReactions.dart';
|
||||||
|
|
||||||
@ -29,6 +31,7 @@ class ChatBubble extends StatefulWidget {
|
|||||||
final GetChatResponseObject bubbleData;
|
final GetChatResponseObject bubbleData;
|
||||||
final GetRoomResponseObject chatData;
|
final GetRoomResponseObject chatData;
|
||||||
final bool isRead;
|
final bool isRead;
|
||||||
|
final String? selfId;
|
||||||
|
|
||||||
final double spacing = 3;
|
final double spacing = 3;
|
||||||
final double timeIconSize = 11;
|
final double timeIconSize = 11;
|
||||||
@ -43,59 +46,31 @@ class ChatBubble extends StatefulWidget {
|
|||||||
required this.chatData,
|
required this.chatData,
|
||||||
required this.refetch,
|
required this.refetch,
|
||||||
this.isRead = false,
|
this.isRead = false,
|
||||||
|
this.selfId,
|
||||||
super.key});
|
super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ChatBubble> createState() => _ChatBubbleState();
|
State<ChatBubble> createState() => _ChatBubbleState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ChatBubbleState extends State<ChatBubble> {
|
class _ChatBubbleState extends State<ChatBubble> with SingleTickerProviderStateMixin {
|
||||||
|
|
||||||
BubbleStyle getSystemStyle() => BubbleStyle(
|
|
||||||
color: AppTheme.isDarkMode(context) ? const Color(0xff182229) : Colors.white,
|
|
||||||
borderWidth: 1,
|
|
||||||
elevation: 2,
|
|
||||||
margin: const BubbleEdges.only(bottom: 20, top: 10),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
);
|
|
||||||
|
|
||||||
BubbleStyle getRemoteStyle(bool seamless) {
|
|
||||||
var color = AppTheme.isDarkMode(context) ? const Color(0xff202c33) : Colors.white;
|
|
||||||
return BubbleStyle(
|
|
||||||
nip: BubbleNip.leftTop,
|
|
||||||
color: seamless ? Colors.transparent : color,
|
|
||||||
borderWidth: seamless ? 0 : 1,
|
|
||||||
elevation: seamless ? 0 : 1,
|
|
||||||
margin: const BubbleEdges.only(bottom: 10, left: 10, right: 50),
|
|
||||||
alignment: Alignment.topLeft,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
BubbleStyle getSelfStyle(bool seamless) {
|
|
||||||
var color = AppTheme.isDarkMode(context) ? const Color(0xff005c4b) : const Color(0xffd3d3d3);
|
|
||||||
return BubbleStyle(
|
|
||||||
nip: BubbleNip.rightBottom,
|
|
||||||
color: seamless ? Colors.transparent : color,
|
|
||||||
borderWidth: seamless ? 0 : 1,
|
|
||||||
elevation: seamless ? 0 : 1,
|
|
||||||
margin: const BubbleEdges.only(bottom: 10, right: 10, left: 50),
|
|
||||||
alignment: Alignment.topRight,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
late ChatMessage message;
|
late ChatMessage message;
|
||||||
double downloadProgress = 0;
|
double downloadProgress = 0;
|
||||||
Future<DownloaderCore>? downloadCore;
|
Future<DownloaderCore>? downloadCore;
|
||||||
|
|
||||||
|
late Offset _position = const Offset(0, 0);
|
||||||
|
late Offset _dragStartPosition = Offset.zero;
|
||||||
|
|
||||||
BubbleStyle getStyle() {
|
BubbleStyle getStyle() {
|
||||||
|
var styles = ChatBubbleStyles(context);
|
||||||
if(widget.bubbleData.messageType == GetRoomResponseObjectMessageType.comment) {
|
if(widget.bubbleData.messageType == GetRoomResponseObjectMessageType.comment) {
|
||||||
if(widget.isSender) {
|
if(widget.isSender) {
|
||||||
return getSelfStyle(false);
|
return styles.getSelfStyle(false);
|
||||||
} else {
|
} else {
|
||||||
return getRemoteStyle(false);
|
return styles.getRemoteStyle(false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return getSystemStyle();
|
return styles.getSystemStyle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,6 +182,17 @@ class _ChatBubbleState extends State<ChatBubble> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Visibility(
|
||||||
|
visible: widget.bubbleData.isReplyable,
|
||||||
|
child: ListTile(
|
||||||
|
leading: const Icon(Icons.reply_outlined),
|
||||||
|
title: const Text('Antworten'),
|
||||||
|
onTap: () => {
|
||||||
|
Provider.of<ChatProps>(context, listen: false).setReferenceMessageId(widget.bubbleData.id, context, widget.chatData.token),
|
||||||
|
Navigator.of(context).pop(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
Visibility(
|
Visibility(
|
||||||
visible: canReact,
|
visible: canReact,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
@ -265,8 +251,10 @@ class _ChatBubbleState extends State<ChatBubble> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
message = ChatMessage(originalMessage: widget.bubbleData.message, originalData: widget.bubbleData.messageParameters);
|
message = ChatMessage(originalMessage: widget.bubbleData.message, originalData: widget.bubbleData.messageParameters);
|
||||||
var showActorDisplayName = widget.bubbleData.messageType == GetRoomResponseObjectMessageType.comment && widget.chatData.type != GetRoomResponseObjectConversationType.oneToOne;
|
var showActorDisplayName = widget.bubbleData.messageType == GetRoomResponseObjectMessageType.comment && widget.chatData.type != GetRoomResponseObjectConversationType.oneToOne;
|
||||||
var showBubbleTime = widget.bubbleData.messageType != GetRoomResponseObjectMessageType.system;
|
var showBubbleTime = widget.bubbleData.messageType != GetRoomResponseObjectMessageType.system
|
||||||
|
&& widget.bubbleData.messageType != GetRoomResponseObjectMessageType.deletedComment;
|
||||||
|
|
||||||
|
var parent = widget.bubbleData.parent;
|
||||||
var actorText = Text(
|
var actorText = Text(
|
||||||
widget.bubbleData.actorDisplayName,
|
widget.bubbleData.actorDisplayName,
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
@ -288,6 +276,24 @@ class _ChatBubbleState extends State<ChatBubble> {
|
|||||||
|
|
||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
|
onHorizontalDragStart: (details) {
|
||||||
|
_dragStartPosition = _position;
|
||||||
|
},
|
||||||
|
onHorizontalDragUpdate: (details) {
|
||||||
|
if(!widget.bubbleData.isReplyable) return;
|
||||||
|
var dx = details.delta.dx - _dragStartPosition.dx;
|
||||||
|
setState(() {
|
||||||
|
_position = (_position.dx + dx).abs() > 30 ? Offset(_position.dx, 0) : Offset(_position.dx + dx, 0);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onHorizontalDragEnd: (DragEndDetails details) {
|
||||||
|
setState(() {
|
||||||
|
_position = const Offset(0, 0);
|
||||||
|
});
|
||||||
|
if(widget.bubbleData.isReplyable) {
|
||||||
|
Provider.of<ChatProps>(context, listen: false).setReferenceMessageId(widget.bubbleData.id, context, widget.chatData.token);
|
||||||
|
}
|
||||||
|
},
|
||||||
onLongPress: showOptionsDialog,
|
onLongPress: showOptionsDialog,
|
||||||
onDoubleTap: showOptionsDialog,
|
onDoubleTap: showOptionsDialog,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -336,9 +342,13 @@ class _ChatBubbleState extends State<ChatBubble> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
child: Transform.translate(
|
||||||
|
offset: _position,
|
||||||
child: Bubble(
|
child: Bubble(
|
||||||
style: getStyle(),
|
style: getStyle(),
|
||||||
child: Container(
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
maxWidth: MediaQuery.of(context).size.width * 0.9,
|
maxWidth: MediaQuery.of(context).size.width * 0.9,
|
||||||
minWidth: showActorDisplayName
|
minWidth: showActorDisplayName
|
||||||
@ -347,10 +357,6 @@ class _ChatBubbleState extends State<ChatBubble> {
|
|||||||
),
|
),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.only(bottom: showBubbleTime ? 18 : 0, top: showActorDisplayName ? 18 : 0),
|
|
||||||
child: message.getWidget()
|
|
||||||
),
|
|
||||||
Visibility(
|
Visibility(
|
||||||
visible: showActorDisplayName,
|
visible: showActorDisplayName,
|
||||||
child: Positioned(
|
child: Positioned(
|
||||||
@ -359,6 +365,23 @@ class _ChatBubbleState extends State<ChatBubble> {
|
|||||||
child: actorText
|
child: actorText
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: showBubbleTime ? 18 : 0, top: showActorDisplayName ? 18 : 0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if(parent != null && widget.bubbleData.messageType == GetRoomResponseObjectMessageType.comment) ...[
|
||||||
|
AnswerReference(
|
||||||
|
context: context,
|
||||||
|
referenceMessage: parent,
|
||||||
|
selfId: widget.selfId,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
],
|
||||||
|
message.getWidget(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
Visibility(
|
Visibility(
|
||||||
visible: showBubbleTime,
|
visible: showBubbleTime,
|
||||||
child: Positioned(
|
child: Positioned(
|
||||||
@ -369,10 +392,11 @@ class _ChatBubbleState extends State<ChatBubble> {
|
|||||||
timeText,
|
timeText,
|
||||||
if(widget.isSender) ...[
|
if(widget.isSender) ...[
|
||||||
SizedBox(width: widget.spacing),
|
SizedBox(width: widget.spacing),
|
||||||
if(widget.isRead)
|
Icon(
|
||||||
Icon(Icons.done_all_outlined, size: widget.timeIconSize, color: widget.timeIconColor)
|
widget.isRead ? Icons.done_all_outlined: Icons.done_outlined,
|
||||||
else
|
size: widget.timeIconSize,
|
||||||
Icon(Icons.done_outlined, size: widget.timeIconSize, color: widget.timeIconColor)
|
color: widget.timeIconColor
|
||||||
|
)
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -390,6 +414,9 @@ class _ChatBubbleState extends State<ChatBubble> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Visibility(
|
Visibility(
|
||||||
@ -417,9 +444,7 @@ class _ChatBubbleState extends State<ChatBubble> {
|
|||||||
DeleteReactMessage(
|
DeleteReactMessage(
|
||||||
chatToken: widget.chatData.token,
|
chatToken: widget.chatData.token,
|
||||||
messageId: widget.bubbleData.id,
|
messageId: widget.bubbleData.id,
|
||||||
params: DeleteReactMessageParams(
|
params: DeleteReactMessageParams(e.key),
|
||||||
e.key
|
|
||||||
),
|
|
||||||
).run().then((value) => widget.refetch(renew: true));
|
).run().then((value) => widget.refetch(renew: true));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -427,11 +452,8 @@ class _ChatBubbleState extends State<ChatBubble> {
|
|||||||
ReactMessage(
|
ReactMessage(
|
||||||
chatToken: widget.chatData.token,
|
chatToken: widget.chatData.token,
|
||||||
messageId: widget.bubbleData.id,
|
messageId: widget.bubbleData.id,
|
||||||
params: ReactMessageParams(
|
params: ReactMessageParams(e.key)
|
||||||
e.key
|
|
||||||
)
|
|
||||||
).run().then((value) => widget.refetch(renew: true));
|
).run().then((value) => widget.refetch(renew: true));
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
54
lib/view/pages/talk/components/chatBubbleStyles.dart
Normal file
54
lib/view/pages/talk/components/chatBubbleStyles.dart
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import 'package:bubble/bubble.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../../../theming/appTheme.dart';
|
||||||
|
|
||||||
|
extension ColorExtensions on Color {
|
||||||
|
Color invert() {
|
||||||
|
final r = 255 - red;
|
||||||
|
final g = 255 - green;
|
||||||
|
final b = 255 - blue;
|
||||||
|
|
||||||
|
return Color.fromARGB((opacity * 255).round(), r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color withWhite(int whiteValue) => Color.fromARGB(alpha, whiteValue, whiteValue, whiteValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChatBubbleStyles {
|
||||||
|
final BuildContext context;
|
||||||
|
|
||||||
|
ChatBubbleStyles(this.context);
|
||||||
|
|
||||||
|
BubbleStyle getSystemStyle() => BubbleStyle(
|
||||||
|
color: AppTheme.isDarkMode(context) ? const Color(0xff182229) : Colors.white,
|
||||||
|
borderWidth: 1,
|
||||||
|
elevation: 2,
|
||||||
|
margin: const BubbleEdges.only(bottom: 20, top: 10),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
);
|
||||||
|
|
||||||
|
BubbleStyle getRemoteStyle(bool seamless) {
|
||||||
|
var color = AppTheme.isDarkMode(context) ? const Color(0xff202c33) : Colors.white;
|
||||||
|
return BubbleStyle(
|
||||||
|
nip: BubbleNip.leftTop,
|
||||||
|
color: seamless ? Colors.transparent : color,
|
||||||
|
borderWidth: seamless ? 0 : 1,
|
||||||
|
elevation: seamless ? 0 : 1,
|
||||||
|
margin: const BubbleEdges.only(bottom: 10, left: 10, right: 50),
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BubbleStyle getSelfStyle(bool seamless) {
|
||||||
|
var color = AppTheme.isDarkMode(context) ? const Color(0xff005c4b) : const Color(0xffd3d3d3);
|
||||||
|
return BubbleStyle(
|
||||||
|
nip: BubbleNip.rightBottom,
|
||||||
|
color: seamless ? Colors.transparent : color,
|
||||||
|
borderWidth: seamless ? 0 : 1,
|
||||||
|
elevation: seamless ? 0 : 1,
|
||||||
|
margin: const BubbleEdges.only(bottom: 10, right: 10, left: 50),
|
||||||
|
alignment: Alignment.topRight,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -15,10 +15,12 @@ import '../../../../storage/base/settingsProvider.dart';
|
|||||||
import '../../../../widget/filePick.dart';
|
import '../../../../widget/filePick.dart';
|
||||||
import '../../../../widget/focusBehaviour.dart';
|
import '../../../../widget/focusBehaviour.dart';
|
||||||
import '../../files/filesUploadDialog.dart';
|
import '../../files/filesUploadDialog.dart';
|
||||||
|
import 'answerReference.dart';
|
||||||
|
|
||||||
class ChatTextfield extends StatefulWidget {
|
class ChatTextfield extends StatefulWidget {
|
||||||
final String sendToToken;
|
final String sendToToken;
|
||||||
const ChatTextfield(this.sendToToken, {super.key});
|
final String? selfId;
|
||||||
|
const ChatTextfield(this.sendToToken, {this.selfId, super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ChatTextfield> createState() => _ChatTextfieldState();
|
State<ChatTextfield> createState() => _ChatTextfieldState();
|
||||||
@ -78,6 +80,8 @@ class _ChatTextfieldState extends State<ChatTextfield> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
settings = Provider.of<SettingsProvider>(context, listen: false);
|
settings = Provider.of<SettingsProvider>(context, listen: false);
|
||||||
|
Provider.of<ChatProps>(context, listen: false).unsafeInternalSetReferenceMessageId =
|
||||||
|
settings.val().talkSettings.draftReplies[widget.sendToToken];
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -91,7 +95,34 @@ class _ChatTextfieldState extends State<ChatTextfield> {
|
|||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.only(left: 10, bottom: 3, top: 3, right: 10),
|
padding: const EdgeInsets.only(left: 10, bottom: 3, top: 3, right: 10),
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: Row(
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Consumer<ChatProps>(
|
||||||
|
builder: (context, data, child) {
|
||||||
|
if(data.getReferenceMessageId != null) {
|
||||||
|
var referenceMessage = data.getChatResponse.sortByTimestamp().where((element) => element.id == data.getReferenceMessageId).last;
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: AnswerReference(
|
||||||
|
context: context,
|
||||||
|
referenceMessage: referenceMessage,
|
||||||
|
selfId: widget.selfId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => data.setReferenceMessageId(null, context, widget.sendToToken),
|
||||||
|
icon: const Icon(Icons.close_outlined),
|
||||||
|
padding: const EdgeInsets.only(left: 0),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: (){
|
onTap: (){
|
||||||
@ -163,20 +194,24 @@ class _ChatTextfieldState extends State<ChatTextfield> {
|
|||||||
const SizedBox(width: 15),
|
const SizedBox(width: 15),
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
mini: true,
|
mini: true,
|
||||||
onPressed: (){
|
onPressed: () {
|
||||||
if(_textBoxController.text.isEmpty) return;
|
if(_textBoxController.text.isEmpty) return;
|
||||||
if(isLoading) return;
|
if(isLoading) return;
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
});
|
});
|
||||||
SendMessage(widget.sendToToken, SendMessageParams(_textBoxController.text)).run().then((value) {
|
SendMessage(widget.sendToToken, SendMessageParams(
|
||||||
|
_textBoxController.text,
|
||||||
|
replyTo: Provider.of<ChatProps>(context, listen: false).getReferenceMessageId.toString()
|
||||||
|
)).run().then((value) {
|
||||||
_query();
|
_query();
|
||||||
setState(() {
|
setState(() {
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
});
|
});
|
||||||
_textBoxController.text = '';
|
_textBoxController.text = '';
|
||||||
setDraft('');
|
setDraft('');
|
||||||
|
Provider.of<ChatProps>(context, listen: false).setReferenceMessageId(null, context, widget.sendToToken);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
backgroundColor: Theme.of(context).primaryColor,
|
backgroundColor: Theme.of(context).primaryColor,
|
||||||
@ -187,6 +222,9 @@ class _ChatTextfieldState extends State<ChatTextfield> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -29,6 +29,7 @@ class DefaultSettings {
|
|||||||
sortFavoritesToTop: true,
|
sortFavoritesToTop: true,
|
||||||
sortUnreadToTop: false,
|
sortUnreadToTop: false,
|
||||||
drafts: {},
|
drafts: {},
|
||||||
|
draftReplies: {},
|
||||||
),
|
),
|
||||||
fileSettings: FileSettings(
|
fileSettings: FileSettings(
|
||||||
sortFoldersToTop: true,
|
sortFoldersToTop: true,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user