mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 17:48:40 +00:00
fix #104
This commit is contained in:
parent
f0e7078ce9
commit
e59e8d81a2
5 changed files with 456 additions and 237 deletions
|
|
@ -1,30 +1,72 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class MediaViewSizing extends StatelessWidget {
|
class MediaViewSizing extends StatefulWidget {
|
||||||
|
const MediaViewSizing(
|
||||||
|
{super.key,
|
||||||
|
this.requiredHeight,
|
||||||
|
required this.child,
|
||||||
|
this.bottomNavigation});
|
||||||
|
|
||||||
|
final double? requiredHeight;
|
||||||
|
final Widget? bottomNavigation;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
const MediaViewSizing(this.child, {super.key});
|
@override
|
||||||
|
State<MediaViewSizing> createState() => _MediaViewSizingState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MediaViewSizingState extends State<MediaViewSizing> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
bool needToDownSizeImage = false;
|
||||||
|
|
||||||
|
if (widget.requiredHeight != null) {
|
||||||
|
// Get the screen size and safe area padding
|
||||||
|
final screenSize = MediaQuery.of(context).size;
|
||||||
|
final safeAreaPadding = MediaQuery.of(context).padding;
|
||||||
|
|
||||||
|
// Calculate the available width and height
|
||||||
|
final availableWidth = screenSize.width;
|
||||||
|
final availableHeight =
|
||||||
|
screenSize.height - safeAreaPadding.top - safeAreaPadding.bottom;
|
||||||
|
|
||||||
|
var aspectRatioWidth = availableWidth;
|
||||||
|
var aspectRatioHeight = (aspectRatioWidth * 16) / 9;
|
||||||
|
if (aspectRatioHeight < availableHeight) {
|
||||||
|
if ((screenSize.height - widget.requiredHeight!) < aspectRatioHeight) {
|
||||||
|
needToDownSizeImage = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget imageChild = Align(
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
child: SizedBox(
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 9 / 16,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(22),
|
||||||
|
child: widget.child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget bottomNavigation = Container();
|
||||||
|
|
||||||
|
if (widget.bottomNavigation != null) {
|
||||||
|
if (needToDownSizeImage) {
|
||||||
|
imageChild = Expanded(child: imageChild);
|
||||||
|
bottomNavigation = widget.bottomNavigation!;
|
||||||
|
} else {
|
||||||
|
bottomNavigation = Expanded(child: widget.bottomNavigation!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: [imageChild, bottomNavigation],
|
||||||
Expanded(
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.topCenter,
|
|
||||||
child: AspectRatio(
|
|
||||||
aspectRatio: 9 / 16,
|
|
||||||
// padding: EdgeInsets.symmetric(vertical: 50, horizontal: 0),
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(22),
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return MediaViewSizing(
|
return MediaViewSizing(
|
||||||
Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(22),
|
borderRadius: BorderRadius.circular(22),
|
||||||
|
|
|
||||||
|
|
@ -294,7 +294,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
child: MediaViewSizing(
|
child: MediaViewSizing(
|
||||||
SizedBox(
|
child: SizedBox(
|
||||||
height: currentImage.height / pixelRatio,
|
height: currentImage.height / pixelRatio,
|
||||||
width: currentImage.width / pixelRatio,
|
width: currentImage.width / pixelRatio,
|
||||||
child: Screenshot(
|
child: Screenshot(
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,27 @@ import 'package:twonly/src/views/chats/media_viewer_view.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/views/contact/contact_view.dart';
|
import 'package:twonly/src/views/contact/contact_view.dart';
|
||||||
|
|
||||||
|
InputDecoration inputTextMessageDeco(BuildContext context) {
|
||||||
|
return InputDecoration(
|
||||||
|
hintText: context.lang.chatListDetailInput,
|
||||||
|
contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
borderSide:
|
||||||
|
BorderSide(color: Theme.of(context).colorScheme.primary, width: 2.0),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(20.0),
|
||||||
|
borderSide:
|
||||||
|
BorderSide(color: Theme.of(context).colorScheme.primary, width: 2.0),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(20.0),
|
||||||
|
borderSide: BorderSide(color: Colors.grey, width: 2.0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
class ChatListEntry extends StatelessWidget {
|
class ChatListEntry extends StatelessWidget {
|
||||||
const ChatListEntry(
|
const ChatListEntry(
|
||||||
this.message, this.contact, this.lastMessageFromSameUser, this.reactions,
|
this.message, this.contact, this.lastMessageFromSameUser, this.reactions,
|
||||||
|
|
@ -61,6 +82,7 @@ class ChatListEntry extends StatelessWidget {
|
||||||
|
|
||||||
if (content is TextMessageContent) {
|
if (content is TextMessageContent) {
|
||||||
hasOneTextReaction = true;
|
hasOneTextReaction = true;
|
||||||
|
if (!isEmoji(content.text)) continue;
|
||||||
late Widget child;
|
late Widget child;
|
||||||
if (EmojiAnimation.animatedIcons.containsKey(content.text)) {
|
if (EmojiAnimation.animatedIcons.containsKey(content.text)) {
|
||||||
child = SizedBox(
|
child = SizedBox(
|
||||||
|
|
@ -90,6 +112,72 @@ class ChatListEntry extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget getTextResponseColumns(BuildContext context, bool right) {
|
||||||
|
List<Widget> children = [];
|
||||||
|
for (final reaction in reactions) {
|
||||||
|
MessageContent? content = MessageContent.fromJson(
|
||||||
|
reaction.kind, jsonDecode(reaction.contentJson!));
|
||||||
|
|
||||||
|
if (content is TextMessageContent) {
|
||||||
|
if (content.text.length <= 1) continue;
|
||||||
|
if (isEmoji(content.text)) continue;
|
||||||
|
var entries = [
|
||||||
|
FaIcon(
|
||||||
|
FontAwesomeIcons.reply,
|
||||||
|
size: 10,
|
||||||
|
),
|
||||||
|
SizedBox(width: 5),
|
||||||
|
Container(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: MediaQuery.of(context).size.width * 0.5,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
content.text,
|
||||||
|
style: TextStyle(fontSize: 14),
|
||||||
|
textAlign: right ? TextAlign.left : TextAlign.right,
|
||||||
|
)),
|
||||||
|
];
|
||||||
|
if (!right) {
|
||||||
|
entries = entries.reversed.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
children.insert(
|
||||||
|
0,
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.only(top: 5, bottom: 0, right: 10, left: 10),
|
||||||
|
child: Container(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 1, horizontal: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: right
|
||||||
|
? const Color.fromARGB(107, 124, 77, 255)
|
||||||
|
: const Color.fromARGB(83, 68, 137, 255),
|
||||||
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: entries,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (children.isEmpty) return Container();
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
// mainAxisAlignment: message.messageOtherId == null
|
||||||
|
// ? MainAxisAlignment.start
|
||||||
|
// : MainAxisAlignment.end,
|
||||||
|
crossAxisAlignment:
|
||||||
|
right ? CrossAxisAlignment.start : CrossAxisAlignment.end,
|
||||||
|
children: children,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
bool right = message.messageOtherId == null;
|
bool right = message.messageOtherId == null;
|
||||||
|
|
@ -194,11 +282,21 @@ class ChatListEntry extends StatelessWidget {
|
||||||
padding: lastMessageFromSameUser
|
padding: lastMessageFromSameUser
|
||||||
? EdgeInsets.only(top: 5, bottom: 0, right: 10, left: 10)
|
? EdgeInsets.only(top: 5, bottom: 0, right: 10, left: 10)
|
||||||
: EdgeInsets.only(top: 5, bottom: 20, right: 10, left: 10),
|
: EdgeInsets.only(top: 5, bottom: 20, right: 10, left: 10),
|
||||||
child: Stack(
|
child: Column(
|
||||||
alignment: right ? Alignment.centerRight : Alignment.centerLeft,
|
mainAxisAlignment:
|
||||||
|
right ? MainAxisAlignment.end : MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment:
|
||||||
|
right ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
child,
|
Stack(
|
||||||
Positioned(bottom: 5, left: 5, right: 5, child: getReactionRow()),
|
alignment: right ? Alignment.centerRight : Alignment.centerLeft,
|
||||||
|
children: [
|
||||||
|
child,
|
||||||
|
Positioned(
|
||||||
|
bottom: 5, left: 5, right: 5, child: getReactionRow()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
getTextResponseColumns(context, !right)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -406,28 +504,7 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
|
||||||
onSubmitted: (_) {
|
onSubmitted: (_) {
|
||||||
_sendMessage();
|
_sendMessage();
|
||||||
},
|
},
|
||||||
decoration: InputDecoration(
|
decoration: inputTextMessageDeco(context),
|
||||||
hintText: context.lang.chatListDetailInput,
|
|
||||||
contentPadding:
|
|
||||||
EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
borderSide: BorderSide(
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
width: 2.0),
|
|
||||||
),
|
|
||||||
focusedBorder: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(20.0),
|
|
||||||
borderSide: BorderSide(
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
width: 2.0),
|
|
||||||
),
|
|
||||||
enabledBorder: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(20.0),
|
|
||||||
borderSide:
|
|
||||||
BorderSide(color: Colors.grey, width: 2.0),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 8),
|
SizedBox(width: 8),
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
Timer? progressTimer;
|
Timer? progressTimer;
|
||||||
|
|
||||||
bool showShortReactions = false;
|
bool showShortReactions = false;
|
||||||
int selectedShortReaction = -1;
|
|
||||||
|
|
||||||
// current image related
|
// current image related
|
||||||
Uint8List? imageBytes;
|
Uint8List? imageBytes;
|
||||||
|
|
@ -42,12 +41,14 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
double progress = 0;
|
double progress = 0;
|
||||||
bool isRealTwonly = false;
|
bool isRealTwonly = false;
|
||||||
bool isDownloading = false;
|
bool isDownloading = false;
|
||||||
|
bool showSendTextMessageInput = false;
|
||||||
|
|
||||||
bool imageSaved = false;
|
bool imageSaved = false;
|
||||||
bool imageSaving = false;
|
bool imageSaving = false;
|
||||||
|
|
||||||
List<Message> allMediaFiles = [];
|
List<Message> allMediaFiles = [];
|
||||||
late StreamSubscription<List<Message>> _subscription;
|
late StreamSubscription<List<Message>> _subscription;
|
||||||
|
TextEditingController textMessageController = TextEditingController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -116,6 +117,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
progress = 0;
|
progress = 0;
|
||||||
isDownloading = false;
|
isDownloading = false;
|
||||||
isRealTwonly = false;
|
isRealTwonly = false;
|
||||||
|
showSendTextMessageInput = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (content.isRealTwonly) {
|
if (content.isRealTwonly) {
|
||||||
|
|
@ -206,6 +208,134 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
_subscription.cancel();
|
_subscription.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future onPressedSaveToGallery() async {
|
||||||
|
if (allMediaFiles.first.messageOtherId == null) {
|
||||||
|
return; // should not be possible
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
imageSaving = true;
|
||||||
|
});
|
||||||
|
encryptAndSendMessage(
|
||||||
|
null,
|
||||||
|
widget.contact.userId,
|
||||||
|
MessageJson(
|
||||||
|
kind: MessageKind.storedMediaFile,
|
||||||
|
messageId: allMediaFiles.first.messageId,
|
||||||
|
content: StoredMediaFileContent(
|
||||||
|
messageId: allMediaFiles.first.messageOtherId!,
|
||||||
|
),
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
),
|
||||||
|
pushKind: PushKind.storedMediaFile,
|
||||||
|
);
|
||||||
|
final res = await saveImageToGallery(imageBytes!);
|
||||||
|
if (res == null) {
|
||||||
|
setState(() {
|
||||||
|
imageSaving = false;
|
||||||
|
imageSaved = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget bottomNavigation() {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
if (maxShowTime == 999999)
|
||||||
|
OutlinedButton(
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
iconColor: imageSaved
|
||||||
|
? Theme.of(context).colorScheme.outline
|
||||||
|
: Theme.of(context).colorScheme.primary,
|
||||||
|
foregroundColor: imageSaved
|
||||||
|
? Theme.of(context).colorScheme.outline
|
||||||
|
: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
onPressed: onPressedSaveToGallery,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
imageSaving
|
||||||
|
? SizedBox(
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
child: CircularProgressIndicator(strokeWidth: 1))
|
||||||
|
: imageSaved
|
||||||
|
? Icon(Icons.check)
|
||||||
|
: FaIcon(FontAwesomeIcons.floppyDisk),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
IconButton(
|
||||||
|
icon: SizedBox(
|
||||||
|
width: 30,
|
||||||
|
height: 30,
|
||||||
|
child: GridView.count(
|
||||||
|
crossAxisCount: 2,
|
||||||
|
children: List.generate(
|
||||||
|
4,
|
||||||
|
(index) {
|
||||||
|
return SizedBox(
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
child: Center(
|
||||||
|
child: EmojiAnimation(
|
||||||
|
emoji:
|
||||||
|
EmojiAnimation.animatedIcons.keys.toList()[index],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
setState(() {
|
||||||
|
showShortReactions = !showShortReactions;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: WidgetStateProperty.all<EdgeInsets>(
|
||||||
|
EdgeInsets.symmetric(vertical: 10, horizontal: 20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
IconButton.outlined(
|
||||||
|
icon: FaIcon(FontAwesomeIcons.message),
|
||||||
|
onPressed: () async {
|
||||||
|
setState(() {
|
||||||
|
showSendTextMessageInput = true;
|
||||||
|
showShortReactions = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: WidgetStateProperty.all<EdgeInsets>(
|
||||||
|
EdgeInsets.symmetric(vertical: 10, horizontal: 20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
IconButton.outlined(
|
||||||
|
icon: FaIcon(FontAwesomeIcons.camera),
|
||||||
|
onPressed: () async {
|
||||||
|
await Navigator.push(context, MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
|
return CameraSendToView(widget.contact);
|
||||||
|
},
|
||||||
|
));
|
||||||
|
},
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: WidgetStateProperty.all<EdgeInsets>(
|
||||||
|
EdgeInsets.symmetric(vertical: 10, horizontal: 20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|
@ -216,10 +346,19 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
if (imageBytes != null && (canBeSeenUntil == null || progress >= 0))
|
if (imageBytes != null && (canBeSeenUntil == null || progress >= 0))
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
if (showSendTextMessageInput) {
|
||||||
|
setState(() {
|
||||||
|
showShortReactions = false;
|
||||||
|
showSendTextMessageInput = false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
nextMediaOrExit();
|
nextMediaOrExit();
|
||||||
},
|
},
|
||||||
child: MediaViewSizing(
|
child: MediaViewSizing(
|
||||||
Image.memory(
|
bottomNavigation: bottomNavigation(),
|
||||||
|
requiredHeight: 50,
|
||||||
|
child: Image.memory(
|
||||||
imageBytes!,
|
imageBytes!,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
frameBuilder:
|
frameBuilder:
|
||||||
|
|
@ -233,7 +372,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
height: 60,
|
height: 60,
|
||||||
width: 60,
|
width: 60,
|
||||||
child:
|
child:
|
||||||
CircularProgressIndicator(strokeWidth: 6),
|
CircularProgressIndicator(strokeWidth: 2),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
|
@ -303,206 +442,77 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
AnimatedPositioned(
|
if (showSendTextMessageInput)
|
||||||
duration: Duration(milliseconds: 200), // Animation duration
|
Positioned(
|
||||||
bottom: showShortReactions ? 100 : 90,
|
bottom: 0,
|
||||||
left: showShortReactions ? 0 : 150,
|
left: 0,
|
||||||
right: showShortReactions ? 0 : 150,
|
right: 0,
|
||||||
curve: Curves.linearToEaseOut,
|
child: Container(
|
||||||
child: AnimatedOpacity(
|
color: context.color.surface,
|
||||||
opacity: showShortReactions ? 1.0 : 0.0, // Fade in/out
|
padding: const EdgeInsets.only(
|
||||||
duration: Duration(milliseconds: 150),
|
bottom: 10, left: 20, right: 20, top: 10),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
children: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
IconButton(
|
||||||
children: List.generate(
|
icon: FaIcon(FontAwesomeIcons.xmark),
|
||||||
6,
|
onPressed: () {
|
||||||
(index) {
|
setState(() {
|
||||||
final emoji =
|
showShortReactions = false;
|
||||||
EmojiAnimation.animatedIcons.keys.toList()[index];
|
showSendTextMessageInput = false;
|
||||||
return AnimatedSize(
|
});
|
||||||
duration:
|
},
|
||||||
Duration(milliseconds: 200), // Animation duration
|
),
|
||||||
curve: Curves.linearToEaseOut,
|
Expanded(
|
||||||
child: GestureDetector(
|
child: Container(
|
||||||
onTap: () {
|
child: TextField(
|
||||||
|
autofocus: true,
|
||||||
|
controller: textMessageController,
|
||||||
|
onEditingComplete: () {
|
||||||
|
setState(() {
|
||||||
|
showSendTextMessageInput = false;
|
||||||
|
showShortReactions = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
decoration: inputTextMessageDeco(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: FaIcon(FontAwesomeIcons.solidPaperPlane),
|
||||||
|
onPressed: () {
|
||||||
|
if (textMessageController.text.isNotEmpty) {
|
||||||
sendTextMessage(
|
sendTextMessage(
|
||||||
widget.contact.userId,
|
widget.contact.userId,
|
||||||
TextMessageContent(
|
TextMessageContent(
|
||||||
text: emoji,
|
text: textMessageController.text,
|
||||||
responseToMessageId:
|
responseToMessageId:
|
||||||
allMediaFiles.first.messageOtherId,
|
allMediaFiles.first.messageOtherId,
|
||||||
),
|
),
|
||||||
PushKind.reaction,
|
PushKind.reaction,
|
||||||
);
|
);
|
||||||
setState(() {
|
textMessageController.clear();
|
||||||
selectedShortReaction = index;
|
}
|
||||||
});
|
setState(() {
|
||||||
Future.delayed(Duration(milliseconds: 300), () {
|
showSendTextMessageInput = false;
|
||||||
setState(() {
|
showShortReactions = false;
|
||||||
showShortReactions = false;
|
});
|
||||||
});
|
},
|
||||||
});
|
)
|
||||||
},
|
],
|
||||||
child: (selectedShortReaction == index)
|
|
||||||
? EmojiAnimationFlying(
|
|
||||||
emoji: emoji,
|
|
||||||
duration: Duration(milliseconds: 300),
|
|
||||||
startPosition: 0.0,
|
|
||||||
size: (showShortReactions) ? 40 : 10)
|
|
||||||
: AnimatedOpacity(
|
|
||||||
opacity: (selectedShortReaction == -1)
|
|
||||||
? 1
|
|
||||||
: 0, // Fade in/out
|
|
||||||
duration: Duration(milliseconds: 150),
|
|
||||||
child: SizedBox(
|
|
||||||
width: showShortReactions ? 40 : 10,
|
|
||||||
child: Center(
|
|
||||||
child: EmojiAnimation(
|
|
||||||
emoji: emoji,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
if (allMediaFiles.isNotEmpty)
|
||||||
if (imageBytes != null)
|
ReactionButtons(
|
||||||
Positioned(
|
show: showShortReactions,
|
||||||
bottom: 30,
|
userId: widget.contact.userId,
|
||||||
left: 0,
|
responseToMessageId: allMediaFiles.first.messageOtherId!,
|
||||||
right: 0,
|
hide: () {
|
||||||
child: Row(
|
setState(() {
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
showShortReactions = false;
|
||||||
children: [
|
showSendTextMessageInput = false;
|
||||||
if (maxShowTime == 999999)
|
});
|
||||||
OutlinedButton(
|
},
|
||||||
style: OutlinedButton.styleFrom(
|
|
||||||
iconColor: imageSaved
|
|
||||||
? Theme.of(context).colorScheme.outline
|
|
||||||
: Theme.of(context).colorScheme.primary,
|
|
||||||
foregroundColor: imageSaved
|
|
||||||
? Theme.of(context).colorScheme.outline
|
|
||||||
: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
onPressed: () async {
|
|
||||||
if (allMediaFiles.first.messageOtherId == null) {
|
|
||||||
return; // should not be possible
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
imageSaving = true;
|
|
||||||
});
|
|
||||||
encryptAndSendMessage(
|
|
||||||
null,
|
|
||||||
widget.contact.userId,
|
|
||||||
MessageJson(
|
|
||||||
kind: MessageKind.storedMediaFile,
|
|
||||||
messageId: allMediaFiles.first.messageId,
|
|
||||||
content: StoredMediaFileContent(
|
|
||||||
messageId: allMediaFiles.first.messageOtherId!,
|
|
||||||
),
|
|
||||||
timestamp: DateTime.now(),
|
|
||||||
),
|
|
||||||
pushKind: PushKind.storedMediaFile,
|
|
||||||
);
|
|
||||||
final res = await saveImageToGallery(imageBytes!);
|
|
||||||
if (res == null) {
|
|
||||||
setState(() {
|
|
||||||
imageSaving = false;
|
|
||||||
imageSaved = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
imageSaving
|
|
||||||
? SizedBox(
|
|
||||||
width: 10,
|
|
||||||
height: 10,
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
strokeWidth: 1))
|
|
||||||
: imageSaved
|
|
||||||
? Icon(Icons.check)
|
|
||||||
: FaIcon(FontAwesomeIcons.floppyDisk),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 10),
|
|
||||||
IconButton(
|
|
||||||
icon: SizedBox(
|
|
||||||
width: 30,
|
|
||||||
height: 30,
|
|
||||||
child: GridView.count(
|
|
||||||
crossAxisCount: 2,
|
|
||||||
children: List.generate(
|
|
||||||
4,
|
|
||||||
(index) {
|
|
||||||
return SizedBox(
|
|
||||||
width: 8,
|
|
||||||
height: 8,
|
|
||||||
child: Center(
|
|
||||||
child: EmojiAnimation(
|
|
||||||
emoji: EmojiAnimation.animatedIcons.keys
|
|
||||||
.toList()[index],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onPressed: () async {
|
|
||||||
setState(() {
|
|
||||||
showShortReactions = !showShortReactions;
|
|
||||||
selectedShortReaction = -1;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
style: ButtonStyle(
|
|
||||||
padding: WidgetStateProperty.all<EdgeInsets>(
|
|
||||||
EdgeInsets.symmetric(vertical: 10, horizontal: 20),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 10),
|
|
||||||
IconButton.outlined(
|
|
||||||
icon: FaIcon(FontAwesomeIcons.message),
|
|
||||||
onPressed: () async {
|
|
||||||
Navigator.popUntil(context, (route) => route.isFirst);
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(builder: (context) {
|
|
||||||
return ChatItemDetailsView(widget.contact);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
style: ButtonStyle(
|
|
||||||
padding: WidgetStateProperty.all<EdgeInsets>(
|
|
||||||
EdgeInsets.symmetric(vertical: 10, horizontal: 20),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 10),
|
|
||||||
IconButton.outlined(
|
|
||||||
icon: FaIcon(FontAwesomeIcons.camera),
|
|
||||||
onPressed: () async {
|
|
||||||
await Navigator.push(context, MaterialPageRoute(
|
|
||||||
builder: (context) {
|
|
||||||
return CameraSendToView(widget.contact);
|
|
||||||
},
|
|
||||||
));
|
|
||||||
},
|
|
||||||
style: ButtonStyle(
|
|
||||||
padding: WidgetStateProperty.all<EdgeInsets>(
|
|
||||||
EdgeInsets.symmetric(vertical: 10, horizontal: 20),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -510,3 +520,93 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ReactionButtons extends StatefulWidget {
|
||||||
|
const ReactionButtons(
|
||||||
|
{super.key,
|
||||||
|
required this.show,
|
||||||
|
required this.userId,
|
||||||
|
required this.responseToMessageId,
|
||||||
|
required this.hide});
|
||||||
|
|
||||||
|
final bool show;
|
||||||
|
final int userId;
|
||||||
|
final int responseToMessageId;
|
||||||
|
final Function() hide;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ReactionButtons> createState() => _ReactionButtonsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ReactionButtonsState extends State<ReactionButtons> {
|
||||||
|
int selectedShortReaction = -1;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AnimatedPositioned(
|
||||||
|
duration: Duration(milliseconds: 200), // Animation duration
|
||||||
|
bottom: widget.show ? 100 : 90,
|
||||||
|
left: widget.show ? 0 : 150,
|
||||||
|
right: widget.show ? 0 : 150,
|
||||||
|
curve: Curves.linearToEaseOut,
|
||||||
|
child: AnimatedOpacity(
|
||||||
|
opacity: widget.show ? 1.0 : 0.0, // Fade in/out
|
||||||
|
duration: Duration(milliseconds: 150),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: List.generate(
|
||||||
|
6,
|
||||||
|
(index) {
|
||||||
|
final emoji = EmojiAnimation.animatedIcons.keys.toList()[index];
|
||||||
|
return AnimatedSize(
|
||||||
|
duration: Duration(milliseconds: 200), // Animation duration
|
||||||
|
curve: Curves.linearToEaseOut,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
sendTextMessage(
|
||||||
|
widget.userId,
|
||||||
|
TextMessageContent(
|
||||||
|
text: emoji,
|
||||||
|
responseToMessageId: widget.responseToMessageId,
|
||||||
|
),
|
||||||
|
PushKind.reaction,
|
||||||
|
);
|
||||||
|
setState(() {
|
||||||
|
selectedShortReaction = index;
|
||||||
|
});
|
||||||
|
Future.delayed(Duration(milliseconds: 300), () {
|
||||||
|
setState(() {
|
||||||
|
widget.hide();
|
||||||
|
selectedShortReaction = -1;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: (selectedShortReaction == index)
|
||||||
|
? EmojiAnimationFlying(
|
||||||
|
emoji: emoji,
|
||||||
|
duration: Duration(milliseconds: 300),
|
||||||
|
startPosition: 0.0,
|
||||||
|
size: (widget.show) ? 40 : 10)
|
||||||
|
: AnimatedOpacity(
|
||||||
|
opacity: (selectedShortReaction == -1)
|
||||||
|
? 1
|
||||||
|
: 0, // Fade in/out
|
||||||
|
duration: Duration(milliseconds: 150),
|
||||||
|
child: SizedBox(
|
||||||
|
width: widget.show ? 40 : 10,
|
||||||
|
child: Center(
|
||||||
|
child: EmojiAnimation(
|
||||||
|
emoji: emoji,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue