mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 09:08: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';
|
||||
|
||||
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;
|
||||
|
||||
const MediaViewSizing(this.child, {super.key});
|
||||
@override
|
||||
State<MediaViewSizing> createState() => _MediaViewSizingState();
|
||||
}
|
||||
|
||||
class _MediaViewSizingState extends State<MediaViewSizing> {
|
||||
@override
|
||||
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(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
children: [imageChild, bottomNavigation],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
);
|
||||
}
|
||||
return MediaViewSizing(
|
||||
Stack(
|
||||
child: Stack(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(22),
|
||||
|
|
|
|||
|
|
@ -294,7 +294,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
setState(() {});
|
||||
},
|
||||
child: MediaViewSizing(
|
||||
SizedBox(
|
||||
child: SizedBox(
|
||||
height: currentImage.height / pixelRatio,
|
||||
width: currentImage.width / pixelRatio,
|
||||
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/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 {
|
||||
const ChatListEntry(
|
||||
this.message, this.contact, this.lastMessageFromSameUser, this.reactions,
|
||||
|
|
@ -61,6 +82,7 @@ class ChatListEntry extends StatelessWidget {
|
|||
|
||||
if (content is TextMessageContent) {
|
||||
hasOneTextReaction = true;
|
||||
if (!isEmoji(content.text)) continue;
|
||||
late Widget child;
|
||||
if (EmojiAnimation.animatedIcons.containsKey(content.text)) {
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
bool right = message.messageOtherId == null;
|
||||
|
|
@ -194,11 +282,21 @@ class ChatListEntry extends StatelessWidget {
|
|||
padding: lastMessageFromSameUser
|
||||
? EdgeInsets.only(top: 5, bottom: 0, right: 10, left: 10)
|
||||
: EdgeInsets.only(top: 5, bottom: 20, right: 10, left: 10),
|
||||
child: Stack(
|
||||
alignment: right ? Alignment.centerRight : Alignment.centerLeft,
|
||||
child: Column(
|
||||
mainAxisAlignment:
|
||||
right ? MainAxisAlignment.end : MainAxisAlignment.start,
|
||||
crossAxisAlignment:
|
||||
right ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
||||
children: [
|
||||
child,
|
||||
Positioned(bottom: 5, left: 5, right: 5, child: getReactionRow()),
|
||||
Stack(
|
||||
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: (_) {
|
||||
_sendMessage();
|
||||
},
|
||||
decoration: 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),
|
||||
),
|
||||
),
|
||||
decoration: inputTextMessageDeco(context),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
Timer? progressTimer;
|
||||
|
||||
bool showShortReactions = false;
|
||||
int selectedShortReaction = -1;
|
||||
|
||||
// current image related
|
||||
Uint8List? imageBytes;
|
||||
|
|
@ -42,12 +41,14 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
double progress = 0;
|
||||
bool isRealTwonly = false;
|
||||
bool isDownloading = false;
|
||||
bool showSendTextMessageInput = false;
|
||||
|
||||
bool imageSaved = false;
|
||||
bool imageSaving = false;
|
||||
|
||||
List<Message> allMediaFiles = [];
|
||||
late StreamSubscription<List<Message>> _subscription;
|
||||
TextEditingController textMessageController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -116,6 +117,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
progress = 0;
|
||||
isDownloading = false;
|
||||
isRealTwonly = false;
|
||||
showSendTextMessageInput = false;
|
||||
});
|
||||
|
||||
if (content.isRealTwonly) {
|
||||
|
|
@ -206,6 +208,134 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
_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
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
|
@ -216,10 +346,19 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
if (imageBytes != null && (canBeSeenUntil == null || progress >= 0))
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
if (showSendTextMessageInput) {
|
||||
setState(() {
|
||||
showShortReactions = false;
|
||||
showSendTextMessageInput = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
nextMediaOrExit();
|
||||
},
|
||||
child: MediaViewSizing(
|
||||
Image.memory(
|
||||
bottomNavigation: bottomNavigation(),
|
||||
requiredHeight: 50,
|
||||
child: Image.memory(
|
||||
imageBytes!,
|
||||
fit: BoxFit.contain,
|
||||
frameBuilder:
|
||||
|
|
@ -233,7 +372,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
height: 60,
|
||||
width: 60,
|
||||
child:
|
||||
CircularProgressIndicator(strokeWidth: 6),
|
||||
CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
);
|
||||
}),
|
||||
|
|
@ -303,206 +442,77 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
],
|
||||
),
|
||||
),
|
||||
AnimatedPositioned(
|
||||
duration: Duration(milliseconds: 200), // Animation duration
|
||||
bottom: showShortReactions ? 100 : 90,
|
||||
left: showShortReactions ? 0 : 150,
|
||||
right: showShortReactions ? 0 : 150,
|
||||
curve: Curves.linearToEaseOut,
|
||||
child: AnimatedOpacity(
|
||||
opacity: showShortReactions ? 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: () {
|
||||
if (showSendTextMessageInput)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
color: context.color.surface,
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 10, left: 20, right: 20, top: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: FaIcon(FontAwesomeIcons.xmark),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
showShortReactions = false;
|
||||
showSendTextMessageInput = false;
|
||||
});
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
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(
|
||||
widget.contact.userId,
|
||||
TextMessageContent(
|
||||
text: emoji,
|
||||
text: textMessageController.text,
|
||||
responseToMessageId:
|
||||
allMediaFiles.first.messageOtherId,
|
||||
),
|
||||
PushKind.reaction,
|
||||
);
|
||||
setState(() {
|
||||
selectedShortReaction = index;
|
||||
});
|
||||
Future.delayed(Duration(milliseconds: 300), () {
|
||||
setState(() {
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
textMessageController.clear();
|
||||
}
|
||||
setState(() {
|
||||
showSendTextMessageInput = false;
|
||||
showShortReactions = false;
|
||||
});
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (imageBytes != null)
|
||||
Positioned(
|
||||
bottom: 30,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.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: () 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),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (allMediaFiles.isNotEmpty)
|
||||
ReactionButtons(
|
||||
show: showShortReactions,
|
||||
userId: widget.contact.userId,
|
||||
responseToMessageId: allMediaFiles.first.messageOtherId!,
|
||||
hide: () {
|
||||
setState(() {
|
||||
showShortReactions = false;
|
||||
showSendTextMessageInput = false;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -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