Encrypted messaging app
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

241 lines
6.3 KiB

  1. import 'package:Capsule/components/view_image.dart';
  2. import 'package:Capsule/models/image_message.dart';
  3. import 'package:Capsule/models/my_profile.dart';
  4. import 'package:Capsule/utils/time.dart';
  5. import 'package:flutter/material.dart';
  6. import '/models/messages.dart';
  7. @immutable
  8. class ConversationMessage extends StatefulWidget {
  9. const ConversationMessage({
  10. Key? key,
  11. required this.message,
  12. required this.profile,
  13. required this.index,
  14. }) : super(key: key);
  15. final Message message;
  16. final MyProfile profile;
  17. final int index;
  18. @override
  19. _ConversationMessageState createState() => _ConversationMessageState();
  20. }
  21. class _ConversationMessageState extends State<ConversationMessage> {
  22. List<PopupMenuEntry<String>> menuItems = [];
  23. Offset? _tapPosition;
  24. bool showDownloadButton = false;
  25. bool showDeleteButton = false;
  26. @override
  27. void initState() {
  28. super.initState();
  29. showDownloadButton = widget.message.runtimeType == ImageMessage;
  30. showDeleteButton = widget.message.senderId == widget.profile.id;
  31. if (showDownloadButton) {
  32. menuItems.add(PopupMenuItem(
  33. value: 'download',
  34. child: Row(
  35. children: const [
  36. Icon(Icons.download),
  37. SizedBox(
  38. width: 10,
  39. ),
  40. Text('Download')
  41. ],
  42. ),
  43. ));
  44. }
  45. if (showDeleteButton) {
  46. menuItems.add(PopupMenuItem(
  47. value: 'delete',
  48. child: Row(
  49. children: const [
  50. Icon(Icons.delete),
  51. SizedBox(
  52. width: 10,
  53. ),
  54. Text('Delete')
  55. ],
  56. ),
  57. ));
  58. }
  59. setState(() {});
  60. }
  61. @override
  62. Widget build(BuildContext context) {
  63. return Container(
  64. padding: const EdgeInsets.only(left: 14,right: 14,top: 0,bottom: 0),
  65. child: Align(
  66. alignment: (
  67. widget.message.senderId == widget.profile.id ?
  68. Alignment.topRight :
  69. Alignment.topLeft
  70. ),
  71. child: Column(
  72. crossAxisAlignment: widget.message.senderId == widget.profile.id ?
  73. CrossAxisAlignment.end :
  74. CrossAxisAlignment.start,
  75. children: <Widget>[
  76. messageContent(context),
  77. const SizedBox(height: 1.5),
  78. Row(
  79. mainAxisAlignment: widget.message.senderId == widget.profile.id ?
  80. MainAxisAlignment.end :
  81. MainAxisAlignment.start,
  82. children: <Widget>[
  83. const SizedBox(width: 10),
  84. usernameOrFailedToSend(),
  85. ],
  86. ),
  87. const SizedBox(height: 1.5),
  88. Row(
  89. mainAxisAlignment: widget.message.senderId == widget.profile.id ?
  90. MainAxisAlignment.end :
  91. MainAxisAlignment.start,
  92. children: <Widget>[
  93. const SizedBox(width: 10),
  94. Text(
  95. convertToAgo(widget.message.createdAt),
  96. textAlign: widget.message.senderId == widget.profile.id ?
  97. TextAlign.left :
  98. TextAlign.right,
  99. style: TextStyle(
  100. fontSize: 12,
  101. color: Colors.grey[500],
  102. ),
  103. ),
  104. ],
  105. ),
  106. widget.index != 0 ?
  107. const SizedBox(height: 20) :
  108. const SizedBox.shrink(),
  109. ],
  110. )
  111. ),
  112. );
  113. }
  114. void _showCustomMenu() {
  115. final Size overlay = MediaQuery.of(context).size;
  116. int addVerticalOffset = 75 * menuItems.length;
  117. // TODO: Implement download & delete methods
  118. showMenu(
  119. context: context,
  120. items: menuItems,
  121. position: RelativeRect.fromRect(
  122. Offset(_tapPosition!.dx, (_tapPosition!.dy - addVerticalOffset)) & const Size(40, 40),
  123. Offset.zero & overlay
  124. )
  125. )
  126. .then<void>((String? delta) async {
  127. if (delta == null) {
  128. return;
  129. }
  130. });
  131. }
  132. void _storePosition(TapDownDetails details) {
  133. _tapPosition = details.globalPosition;
  134. }
  135. Widget messageContent(BuildContext context) {
  136. if (widget.message.runtimeType == ImageMessage) {
  137. return GestureDetector(
  138. onTap: () {
  139. Navigator.push(context, MaterialPageRoute(builder: (context) {
  140. return ViewImage(
  141. message: (widget.message as ImageMessage)
  142. );
  143. }));
  144. },
  145. onLongPress: _showCustomMenu,
  146. onTapDown: _storePosition,
  147. child: ConstrainedBox(
  148. constraints: const BoxConstraints(maxHeight: 350, maxWidth: 250),
  149. child: ClipRRect(
  150. borderRadius: BorderRadius.circular(20), child: Image.file(
  151. (widget.message as ImageMessage).file,
  152. fit: BoxFit.fill,
  153. ),
  154. ),
  155. ),
  156. );
  157. }
  158. return GestureDetector(
  159. onLongPress: _showCustomMenu,
  160. onTapDown: _storePosition,
  161. child: Container(
  162. decoration: BoxDecoration(
  163. borderRadius: BorderRadius.circular(20),
  164. color: (
  165. widget.message.senderId == widget.profile.id ?
  166. Theme.of(context).colorScheme.primary :
  167. Theme.of(context).colorScheme.tertiary
  168. ),
  169. ),
  170. padding: const EdgeInsets.all(12),
  171. child: Text(
  172. widget.message.getContent(),
  173. style: TextStyle(
  174. fontSize: 15,
  175. color: widget.message.senderId == widget.profile.id ?
  176. Theme.of(context).colorScheme.onPrimary :
  177. Theme.of(context).colorScheme.onTertiary,
  178. ),
  179. ),
  180. ),
  181. );
  182. }
  183. Widget usernameOrFailedToSend() {
  184. if (widget.message.senderId != widget.profile.id) {
  185. return Text(
  186. widget.message.senderUsername,
  187. style: TextStyle(
  188. fontSize: 12,
  189. color: Colors.grey[300],
  190. ),
  191. );
  192. }
  193. if (widget.message.failedToSend) {
  194. return Row(
  195. mainAxisAlignment: MainAxisAlignment.end,
  196. children: const <Widget>[
  197. Icon(
  198. Icons.warning_rounded,
  199. color: Colors.red,
  200. size: 20,
  201. ),
  202. Text(
  203. 'Failed to send',
  204. style: TextStyle(color: Colors.red, fontSize: 12),
  205. textAlign: TextAlign.right,
  206. ),
  207. ],
  208. );
  209. }
  210. return const SizedBox.shrink();
  211. }
  212. }