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;
|
||||
List<String>? reactionsSelf;
|
||||
@JsonKey(fromJson: _fromJson) Map<String, RichObjectString>? messageParameters;
|
||||
GetChatResponseObject? parent;
|
||||
|
||||
GetChatResponseObject(
|
||||
this.id,
|
||||
@ -53,7 +54,8 @@ class GetChatResponseObject {
|
||||
this.message,
|
||||
this.messageParameters,
|
||||
this.reactions,
|
||||
this.reactionsSelf
|
||||
this.reactionsSelf,
|
||||
this.parent,
|
||||
);
|
||||
|
||||
factory GetChatResponseObject.fromJson(Map<String, dynamic> json) => _$GetChatResponseObjectFromJson(json);
|
||||
@ -78,7 +80,8 @@ class GetChatResponseObject {
|
||||
text,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
}
|
||||
|
@ -52,6 +52,10 @@ GetChatResponseObject _$GetChatResponseObjectFromJson(
|
||||
(json['reactionsSelf'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList(),
|
||||
json['parent'] == null
|
||||
? null
|
||||
: GetChatResponseObject.fromJson(
|
||||
json['parent'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$GetChatResponseObjectToJson(
|
||||
@ -74,6 +78,7 @@ Map<String, dynamic> _$GetChatResponseObjectToJson(
|
||||
'reactionsSelf': instance.reactionsSelf,
|
||||
'messageParameters':
|
||||
instance.messageParameters?.map((k, e) => MapEntry(k, e.toJson())),
|
||||
'parent': instance.parent?.toJson(),
|
||||
};
|
||||
|
||||
const _$GetRoomResponseObjectMessageActorTypeEnumMap = {
|
||||
|
@ -7,7 +7,7 @@ part 'sendMessageParams.g.dart';
|
||||
@JsonSerializable(explicitToJson: true, includeIfNull: false)
|
||||
class SendMessageParams extends ApiParams {
|
||||
String message;
|
||||
int? replyTo;
|
||||
String? replyTo;
|
||||
|
||||
SendMessageParams(this.message, {this.replyTo});
|
||||
|
||||
|
@ -9,7 +9,7 @@ part of 'sendMessageParams.dart';
|
||||
SendMessageParams _$SendMessageParamsFromJson(Map<String, dynamic> json) =>
|
||||
SendMessageParams(
|
||||
json['message'] as String,
|
||||
replyTo: json['replyTo'] as int?,
|
||||
replyTo: json['replyTo'] as String?,
|
||||
);
|
||||
|
||||
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/server/userIndex/update/updateUserindex.dart';
|
||||
import 'main.dart';
|
||||
import 'model/breakers/Breaker.dart';
|
||||
import 'model/breakers/BreakerProps.dart';
|
||||
import 'model/chatList/chatListProps.dart';
|
||||
@ -28,13 +29,12 @@ import 'view/pages/timetable/timetable.dart';
|
||||
class App extends StatefulWidget {
|
||||
const App({super.key});
|
||||
|
||||
static PersistentTabController bottomNavigator = PersistentTabController(initialIndex: 0);
|
||||
|
||||
@override
|
||||
State<App> createState() => _AppState();
|
||||
}
|
||||
|
||||
class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
|
||||
late Timer refetchChats;
|
||||
late Timer updateTimings;
|
||||
|
||||
@ -58,6 +58,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
Main.bottomNavigator = PersistentTabController(initialIndex: 0);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
Provider.of<BreakerProps>(context, listen: false).run();
|
||||
@ -95,7 +96,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => PersistentTabView(
|
||||
controller: App.bottomNavigator,
|
||||
controller: Main.bottomNavigator,
|
||||
navBarOverlap: const NavBarOverlap.none(),
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
|
||||
|
@ -9,6 +9,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:jiffy/jiffy.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:flutter_localizations/flutter_localizations.dart';
|
||||
|
||||
@ -74,6 +75,8 @@ Future<void> main() async {
|
||||
|
||||
class Main extends StatefulWidget {
|
||||
const Main({super.key});
|
||||
static PersistentTabController bottomNavigator = PersistentTabController(initialIndex: 0);
|
||||
|
||||
|
||||
@override
|
||||
State<Main> createState() => _MainState();
|
||||
|
@ -1,15 +1,23 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../api/apiResponse.dart';
|
||||
import '../../api/marianumcloud/talk/chat/getChatCache.dart';
|
||||
import '../../api/marianumcloud/talk/chat/getChatResponse.dart';
|
||||
import '../../storage/base/settingsProvider.dart';
|
||||
import '../dataHolder.dart';
|
||||
|
||||
class ChatProps extends DataHolder {
|
||||
String _queryToken = '';
|
||||
DateTime _lastTokenSet = DateTime.now();
|
||||
int? _referenceMessageId;
|
||||
|
||||
GetChatResponse? _getChatResponse;
|
||||
GetChatResponse get getChatResponse => _getChatResponse!;
|
||||
|
||||
int? get getReferenceMessageId => _referenceMessageId;
|
||||
set unsafeInternalSetReferenceMessageId(int? reference) => _referenceMessageId = reference;
|
||||
|
||||
@override
|
||||
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) {
|
||||
_queryToken = token;
|
||||
_getChatResponse = null;
|
||||
|
@ -3,7 +3,7 @@ import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_app_badger/flutter_app_badger.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../app.dart';
|
||||
import '../main.dart';
|
||||
import '../model/chatList/chatListProps.dart';
|
||||
import '../model/chatList/chatProps.dart';
|
||||
|
||||
@ -18,6 +18,6 @@ class NotificationTasks {
|
||||
}
|
||||
|
||||
static void navigateToTalk() {
|
||||
App.bottomNavigator.jumpToTab(1);
|
||||
Main.bottomNavigator.jumpToTab(1);
|
||||
}
|
||||
}
|
||||
|
@ -7,8 +7,9 @@ class TalkSettings {
|
||||
bool sortFavoritesToTop;
|
||||
bool sortUnreadToTop;
|
||||
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);
|
||||
Map<String, dynamic> toJson() => _$TalkSettingsToJson(this);
|
||||
|
@ -10,6 +10,7 @@ TalkSettings _$TalkSettingsFromJson(Map<String, dynamic> json) => TalkSettings(
|
||||
sortFavoritesToTop: json['sortFavoritesToTop'] as bool,
|
||||
sortUnreadToTop: json['sortUnreadToTop'] as bool,
|
||||
drafts: Map<String, String>.from(json['drafts'] as Map),
|
||||
draftReplies: Map<String, int>.from(json['draftReplies'] as Map),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$TalkSettingsToJson(TalkSettings instance) =>
|
||||
@ -17,4 +18,5 @@ Map<String, dynamic> _$TalkSettingsToJson(TalkSettings instance) =>
|
||||
'sortFavoritesToTop': instance.sortFavoritesToTop,
|
||||
'sortUnreadToTop': instance.sortUnreadToTop,
|
||||
'drafts': instance.drafts,
|
||||
'draftReplies': instance.draftReplies,
|
||||
};
|
||||
|
@ -71,6 +71,7 @@ class _ChatViewState extends State<ChatView> {
|
||||
chatData: widget.room,
|
||||
refetch: _query,
|
||||
isRead: element.id <= commonRead,
|
||||
selfId: widget.selfId,
|
||||
)
|
||||
);
|
||||
});
|
||||
@ -129,8 +130,8 @@ class _ChatViewState extends State<ChatView> {
|
||||
Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: TalkNavigator.isSecondaryVisible(context)
|
||||
? ChatTextfield(widget.room.token)
|
||||
: SafeArea(child: ChatTextfield(widget.room.token)
|
||||
? ChatTextfield(widget.room.token, selfId: widget.selfId)
|
||||
: 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:emoji_picker_flutter/emoji_picker_flutter.dart' as emojis;
|
||||
import 'package:flowder/flowder.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.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/room/getRoomResponse.dart';
|
||||
import '../../../../model/chatList/chatProps.dart';
|
||||
import '../../../../theming/appTheme.dart';
|
||||
import '../../../../widget/debug/debugTile.dart';
|
||||
import '../../files/fileElement.dart';
|
||||
import 'answerReference.dart';
|
||||
import 'chatBubbleStyles.dart';
|
||||
import 'chatMessage.dart';
|
||||
import '../messageReactions.dart';
|
||||
|
||||
@ -29,6 +31,7 @@ class ChatBubble extends StatefulWidget {
|
||||
final GetChatResponseObject bubbleData;
|
||||
final GetRoomResponseObject chatData;
|
||||
final bool isRead;
|
||||
final String? selfId;
|
||||
|
||||
final double spacing = 3;
|
||||
final double timeIconSize = 11;
|
||||
@ -43,59 +46,31 @@ class ChatBubble extends StatefulWidget {
|
||||
required this.chatData,
|
||||
required this.refetch,
|
||||
this.isRead = false,
|
||||
this.selfId,
|
||||
super.key});
|
||||
|
||||
@override
|
||||
State<ChatBubble> createState() => _ChatBubbleState();
|
||||
}
|
||||
|
||||
class _ChatBubbleState extends State<ChatBubble> {
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
class _ChatBubbleState extends State<ChatBubble> with SingleTickerProviderStateMixin {
|
||||
late ChatMessage message;
|
||||
double downloadProgress = 0;
|
||||
Future<DownloaderCore>? downloadCore;
|
||||
|
||||
late Offset _position = const Offset(0, 0);
|
||||
late Offset _dragStartPosition = Offset.zero;
|
||||
|
||||
BubbleStyle getStyle() {
|
||||
var styles = ChatBubbleStyles(context);
|
||||
if(widget.bubbleData.messageType == GetRoomResponseObjectMessageType.comment) {
|
||||
if(widget.isSender) {
|
||||
return getSelfStyle(false);
|
||||
return styles.getSelfStyle(false);
|
||||
} else {
|
||||
return getRemoteStyle(false);
|
||||
return styles.getRemoteStyle(false);
|
||||
}
|
||||
} 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(
|
||||
visible: canReact,
|
||||
child: ListTile(
|
||||
@ -265,8 +251,10 @@ class _ChatBubbleState extends State<ChatBubble> {
|
||||
Widget build(BuildContext context) {
|
||||
message = ChatMessage(originalMessage: widget.bubbleData.message, originalData: widget.bubbleData.messageParameters);
|
||||
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(
|
||||
widget.bubbleData.actorDisplayName,
|
||||
textAlign: TextAlign.start,
|
||||
@ -288,6 +276,24 @@ class _ChatBubbleState extends State<ChatBubble> {
|
||||
|
||||
children: [
|
||||
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,
|
||||
onDoubleTap: showOptionsDialog,
|
||||
onTap: () {
|
||||
@ -336,55 +342,76 @@ class _ChatBubbleState extends State<ChatBubble> {
|
||||
}
|
||||
});
|
||||
},
|
||||
child: Bubble(
|
||||
style: getStyle(),
|
||||
child: Container(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * 0.9,
|
||||
minWidth: showActorDisplayName
|
||||
? actorText.size.width
|
||||
: timeText.size.width + (widget.isSender ? widget.spacing + widget.timeIconSize : 0) + 3,
|
||||
),
|
||||
child: Stack(
|
||||
child: Transform.translate(
|
||||
offset: _position,
|
||||
child: Bubble(
|
||||
style: getStyle(),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(bottom: showBubbleTime ? 18 : 0, top: showActorDisplayName ? 18 : 0),
|
||||
child: message.getWidget()
|
||||
),
|
||||
Visibility(
|
||||
visible: showActorDisplayName,
|
||||
child: Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
child: actorText
|
||||
Container(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * 0.9,
|
||||
minWidth: showActorDisplayName
|
||||
? actorText.size.width
|
||||
: timeText.size.width + (widget.isSender ? widget.spacing + widget.timeIconSize : 0) + 3,
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: showBubbleTime,
|
||||
child: Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: Row(
|
||||
children: [
|
||||
timeText,
|
||||
if(widget.isSender) ...[
|
||||
SizedBox(width: widget.spacing),
|
||||
if(widget.isRead)
|
||||
Icon(Icons.done_all_outlined, size: widget.timeIconSize, color: widget.timeIconColor)
|
||||
else
|
||||
Icon(Icons.done_outlined, size: widget.timeIconSize, color: widget.timeIconColor)
|
||||
]
|
||||
],
|
||||
)
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: downloadProgress > 0,
|
||||
child: Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
child: LinearProgressIndicator(value: downloadProgress/100),
|
||||
child: Stack(
|
||||
children: [
|
||||
Visibility(
|
||||
visible: showActorDisplayName,
|
||||
child: Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
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(
|
||||
visible: showBubbleTime,
|
||||
child: Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: Row(
|
||||
children: [
|
||||
timeText,
|
||||
if(widget.isSender) ...[
|
||||
SizedBox(width: widget.spacing),
|
||||
Icon(
|
||||
widget.isRead ? Icons.done_all_outlined: Icons.done_outlined,
|
||||
size: widget.timeIconSize,
|
||||
color: widget.timeIconColor
|
||||
)
|
||||
]
|
||||
],
|
||||
)
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: downloadProgress > 0,
|
||||
child: Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
child: LinearProgressIndicator(value: downloadProgress/100),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -417,9 +444,7 @@ class _ChatBubbleState extends State<ChatBubble> {
|
||||
DeleteReactMessage(
|
||||
chatToken: widget.chatData.token,
|
||||
messageId: widget.bubbleData.id,
|
||||
params: DeleteReactMessageParams(
|
||||
e.key
|
||||
),
|
||||
params: DeleteReactMessageParams(e.key),
|
||||
).run().then((value) => widget.refetch(renew: true));
|
||||
|
||||
} else {
|
||||
@ -427,11 +452,8 @@ class _ChatBubbleState extends State<ChatBubble> {
|
||||
ReactMessage(
|
||||
chatToken: widget.chatData.token,
|
||||
messageId: widget.bubbleData.id,
|
||||
params: ReactMessageParams(
|
||||
e.key
|
||||
)
|
||||
params: ReactMessageParams(e.key)
|
||||
).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/focusBehaviour.dart';
|
||||
import '../../files/filesUploadDialog.dart';
|
||||
import 'answerReference.dart';
|
||||
|
||||
class ChatTextfield extends StatefulWidget {
|
||||
final String sendToToken;
|
||||
const ChatTextfield(this.sendToToken, {super.key});
|
||||
final String? selfId;
|
||||
const ChatTextfield(this.sendToToken, {this.selfId, super.key});
|
||||
|
||||
@override
|
||||
State<ChatTextfield> createState() => _ChatTextfieldState();
|
||||
@ -78,6 +80,8 @@ class _ChatTextfieldState extends State<ChatTextfield> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
settings = Provider.of<SettingsProvider>(context, listen: false);
|
||||
Provider.of<ChatProps>(context, listen: false).unsafeInternalSetReferenceMessageId =
|
||||
settings.val().talkSettings.draftReplies[widget.sendToToken];
|
||||
}
|
||||
|
||||
@override
|
||||
@ -91,102 +95,136 @@ class _ChatTextfieldState extends State<ChatTextfield> {
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(left: 10, bottom: 3, top: 3, right: 10),
|
||||
width: double.infinity,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
onTap: (){
|
||||
showDialog(context: context, builder: (context) => SimpleDialog(
|
||||
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: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.file_open),
|
||||
title: const Text('Aus Dateien auswählen'),
|
||||
onTap: () {
|
||||
FilePick.documentPick().then(mediaUpload);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
Visibility(
|
||||
visible: !Platform.isIOS,
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.image),
|
||||
title: const Text('Aus Gallerie auswählen'),
|
||||
onTap: () {
|
||||
FilePick.multipleGalleryPick().then((value) {
|
||||
if(value != null) mediaUpload(value.map((e) => e.path).toList());
|
||||
});
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
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();
|
||||
}
|
||||
},
|
||||
child: Material(
|
||||
elevation: 5,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
GestureDetector(
|
||||
onTap: (){
|
||||
showDialog(context: context, builder: (context) => SimpleDialog(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.file_open),
|
||||
title: const Text('Aus Dateien auswählen'),
|
||||
onTap: () {
|
||||
FilePick.documentPick().then(mediaUpload);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
Visibility(
|
||||
visible: !Platform.isIOS,
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.image),
|
||||
title: const Text('Aus Gallerie auswählen'),
|
||||
onTap: () {
|
||||
FilePick.multipleGalleryPick().then((value) {
|
||||
if(value != null) mediaUpload(value.map((e) => e.path).toList());
|
||||
});
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
));
|
||||
},
|
||||
child: Material(
|
||||
elevation: 5,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: Container(
|
||||
height: 30,
|
||||
width: 30,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).primaryColor,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: const Icon(Icons.attach_file_outlined, color: Colors.white, size: 20, ),
|
||||
),
|
||||
)
|
||||
),
|
||||
child: Container(
|
||||
height: 30,
|
||||
width: 30,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).primaryColor,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
const SizedBox(width: 15),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
autocorrect: true,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
controller: _textBoxController,
|
||||
maxLines: 7,
|
||||
minLines: 1,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Nachricht schreiben...',
|
||||
border: InputBorder.none,
|
||||
),
|
||||
onChanged: (String text) {
|
||||
if(text.trim().toLowerCase() == 'marbot marbot marbot') {
|
||||
var newText = 'Roboter sind cool und so, aber Marbots sind besser!';
|
||||
_textBoxController.text = newText;
|
||||
text = newText;
|
||||
}
|
||||
setDraft(text);
|
||||
},
|
||||
onTapOutside: (PointerDownEvent event) => FocusBehaviour.textFieldTapOutside(context),
|
||||
),
|
||||
child: const Icon(Icons.attach_file_outlined, color: Colors.white, size: 20, ),
|
||||
),
|
||||
)
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
autocorrect: true,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
controller: _textBoxController,
|
||||
maxLines: 7,
|
||||
minLines: 1,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Nachricht schreiben...',
|
||||
border: InputBorder.none,
|
||||
),
|
||||
onChanged: (String text) {
|
||||
if(text.trim().toLowerCase() == 'marbot marbot marbot') {
|
||||
var newText = 'Roboter sind cool und so, aber Marbots sind besser!';
|
||||
_textBoxController.text = newText;
|
||||
text = newText;
|
||||
}
|
||||
setDraft(text);
|
||||
},
|
||||
onTapOutside: (PointerDownEvent event) => FocusBehaviour.textFieldTapOutside(context),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
FloatingActionButton(
|
||||
mini: true,
|
||||
onPressed: (){
|
||||
if(_textBoxController.text.isEmpty) return;
|
||||
if(isLoading) return;
|
||||
const SizedBox(width: 15),
|
||||
FloatingActionButton(
|
||||
mini: true,
|
||||
onPressed: () {
|
||||
if(_textBoxController.text.isEmpty) return;
|
||||
if(isLoading) return;
|
||||
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
SendMessage(widget.sendToToken, SendMessageParams(_textBoxController.text)).run().then((value) {
|
||||
_query();
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
_textBoxController.text = '';
|
||||
setDraft('');
|
||||
});
|
||||
},
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
elevation: 5,
|
||||
child: isLoading
|
||||
? Container(padding: const EdgeInsets.all(10), child: const CircularProgressIndicator(color: Colors.white, strokeWidth: 2))
|
||||
: const Icon(Icons.send, color: Colors.white, size: 18),
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
SendMessage(widget.sendToToken, SendMessageParams(
|
||||
_textBoxController.text,
|
||||
replyTo: Provider.of<ChatProps>(context, listen: false).getReferenceMessageId.toString()
|
||||
)).run().then((value) {
|
||||
_query();
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
_textBoxController.text = '';
|
||||
setDraft('');
|
||||
Provider.of<ChatProps>(context, listen: false).setReferenceMessageId(null, context, widget.sendToToken);
|
||||
});
|
||||
},
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
elevation: 5,
|
||||
child: isLoading
|
||||
? Container(padding: const EdgeInsets.all(10), child: const CircularProgressIndicator(color: Colors.white, strokeWidth: 2))
|
||||
: const Icon(Icons.send, color: Colors.white, size: 18),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -29,6 +29,7 @@ class DefaultSettings {
|
||||
sortFavoritesToTop: true,
|
||||
sortUnreadToTop: false,
|
||||
drafts: {},
|
||||
draftReplies: {},
|
||||
),
|
||||
fileSettings: FileSettings(
|
||||
sortFoldersToTop: true,
|
||||
|
Loading…
x
Reference in New Issue
Block a user