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:
Lars Neuhaus 2024-05-12 12:36:14 +00:00
commit 430f2fe603
16 changed files with 402 additions and 190 deletions

View File

@ -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,
);
}

View File

@ -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 = {

View File

@ -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});

View File

@ -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) {

View File

@ -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,

View File

@ -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();

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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,
};

View File

@ -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)
),
)
],

View 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,
),
),
],
),
),
);
}
}

View File

@ -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));
}
},
),

View 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,
);
}
}

View File

@ -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),
),
],
),
],
),
),
),
],

View File

@ -29,6 +29,7 @@ class DefaultSettings {
sortFavoritesToTop: true,
sortUnreadToTop: false,
drafts: {},
draftReplies: {},
),
fileSettings: FileSettings(
sortFoldersToTop: true,