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.

313 lines
9.2 KiB

  1. import 'dart:io';
  2. import 'package:Envelope/database/repositories/messages_repository.dart';
  3. import 'package:Envelope/services/messages_service.dart';
  4. import 'package:Envelope/views/main/conversation/message.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:image_picker/image_picker.dart';
  7. import '/components/custom_title_bar.dart';
  8. import '/components/file_picker.dart';
  9. import '/database/models/conversations.dart';
  10. import '/database/models/messages.dart';
  11. import '/database/models/my_profile.dart';
  12. import '/views/main/conversation/settings.dart';
  13. class ConversationDetail extends StatefulWidget{
  14. final Conversation conversation;
  15. const ConversationDetail({
  16. Key? key,
  17. required this.conversation,
  18. }) : super(key: key);
  19. @override
  20. _ConversationDetailState createState() => _ConversationDetailState();
  21. }
  22. class _ConversationDetailState extends State<ConversationDetail> {
  23. List<Message> messages = [];
  24. MyProfile profile = MyProfile(
  25. id: '',
  26. username: '',
  27. messageExpiryDefault: 'no_expiry',
  28. );
  29. TextEditingController msgController = TextEditingController();
  30. bool showFilePicker = false;
  31. List<File> selectedImages = [];
  32. bool sendDisabled = false;
  33. @override
  34. Widget build(BuildContext context) {
  35. return Scaffold(
  36. appBar: CustomTitleBar(
  37. title: Text(
  38. widget.conversation.name,
  39. style: TextStyle(
  40. fontSize: 16,
  41. fontWeight: FontWeight.w600,
  42. color: Theme.of(context).appBarTheme.toolbarTextStyle?.color
  43. ),
  44. ),
  45. showBack: true,
  46. rightHandButton: IconButton(
  47. onPressed: (){
  48. Navigator.of(context).push(
  49. MaterialPageRoute(builder: (context) => ConversationSettings(
  50. conversation: widget.conversation
  51. )),
  52. );
  53. },
  54. icon: Icon(
  55. Icons.settings,
  56. color: Theme.of(context).appBarTheme.iconTheme?.color,
  57. ),
  58. ),
  59. ),
  60. body: Stack(
  61. children: <Widget>[
  62. messagesView(),
  63. newMessageContent(),
  64. ],
  65. ),
  66. );
  67. }
  68. Future<void> fetchMessages() async {
  69. profile = await MyProfile.getProfile();
  70. messages = await MessagesRepository.getMessagesForThread(widget.conversation);
  71. setState(() {});
  72. }
  73. @override
  74. void initState() {
  75. sendDisabled = widget.conversation.adminSendMessages && !widget.conversation.admin;
  76. super.initState();
  77. fetchMessages();
  78. }
  79. Widget messagesView() {
  80. if (messages.isEmpty) {
  81. return const Center(
  82. child: Text('No Messages'),
  83. );
  84. }
  85. return ListView.builder(
  86. itemCount: messages.length,
  87. shrinkWrap: true,
  88. padding: EdgeInsets.only(
  89. top: 10,
  90. bottom: selectedImages.isEmpty ? 90 : 160,
  91. ),
  92. reverse: true,
  93. itemBuilder: (context, index) {
  94. return ConversationMessage(
  95. message: messages[index],
  96. profile: profile,
  97. index: index,
  98. );
  99. },
  100. );
  101. }
  102. Widget showSelectedImages() {
  103. if (selectedImages.isEmpty) {
  104. return const SizedBox.shrink();
  105. }
  106. return SizedBox(
  107. height: 80,
  108. width: double.infinity,
  109. child: ListView.builder(
  110. itemCount: selectedImages.length,
  111. shrinkWrap: true,
  112. scrollDirection: Axis.horizontal,
  113. padding: const EdgeInsets.all(5),
  114. itemBuilder: (context, i) {
  115. return Stack(
  116. children: [
  117. Column(
  118. children: [
  119. const SizedBox(height: 5),
  120. Container(
  121. alignment: Alignment.center,
  122. height: 65,
  123. width: 65,
  124. child: Image.file(
  125. selectedImages[i],
  126. fit: BoxFit.fill,
  127. ),
  128. ),
  129. ],
  130. ),
  131. SizedBox(
  132. height: 60,
  133. width: 70,
  134. child: Align(
  135. alignment: Alignment.topRight,
  136. child: GestureDetector(
  137. onTap: () {
  138. setState(() {
  139. selectedImages.removeAt(i);
  140. });
  141. },
  142. child: Container(
  143. height: 20,
  144. width: 20,
  145. decoration: BoxDecoration(
  146. color: Theme.of(context).colorScheme.onPrimary,
  147. borderRadius: BorderRadius.circular(30),
  148. ),
  149. child: Icon(
  150. Icons.cancel,
  151. color: Theme.of(context).primaryColor,
  152. size: 20
  153. ),
  154. ),
  155. ),
  156. ),
  157. ),
  158. ],
  159. );
  160. },
  161. )
  162. );
  163. }
  164. Widget newMessageContent() {
  165. return Align(
  166. alignment: Alignment.bottomLeft,
  167. child: ConstrainedBox(
  168. constraints: BoxConstraints(
  169. maxHeight: selectedImages.isEmpty ?
  170. 200.0 :
  171. 270.0,
  172. ),
  173. child: Container(
  174. padding: const EdgeInsets.only(left: 10,bottom: 10,top: 10),
  175. width: double.infinity,
  176. color: Theme.of(context).backgroundColor,
  177. child: Column(
  178. mainAxisSize: MainAxisSize.min,
  179. children: [
  180. showSelectedImages(),
  181. Row(
  182. children: <Widget>[
  183. GestureDetector(
  184. onTap: (){
  185. setState(() {
  186. if (sendDisabled) {
  187. return;
  188. }
  189. showFilePicker = !showFilePicker;
  190. });
  191. },
  192. child: Container(
  193. height: 30,
  194. width: 30,
  195. decoration: BoxDecoration(
  196. color: Theme.of(context).primaryColor,
  197. borderRadius: BorderRadius.circular(30),
  198. ),
  199. child: Icon(
  200. Icons.add,
  201. color: Theme.of(context).colorScheme.onPrimary,
  202. size: 20
  203. ),
  204. ),
  205. ),
  206. const SizedBox(width: 15,),
  207. Expanded(
  208. child: TextField(
  209. enabled: !sendDisabled,
  210. decoration: InputDecoration(
  211. hintText: sendDisabled ?
  212. 'Messages disabled for non-admins' :
  213. 'Write message...',
  214. hintStyle: TextStyle(
  215. color: Theme.of(context).hintColor,
  216. ),
  217. border: InputBorder.none,
  218. ),
  219. maxLines: null,
  220. controller: msgController,
  221. ),
  222. ),
  223. const SizedBox(width: 15),
  224. SizedBox(
  225. width: 45,
  226. height: 45,
  227. child: FittedBox(
  228. child: FloatingActionButton(
  229. onPressed: () async {
  230. if ((msgController.text == '' && selectedImages.isEmpty) || sendDisabled) {
  231. return;
  232. }
  233. await MessagesService.sendMessage(
  234. widget.conversation,
  235. data: msgController.text != '' ? msgController.text : null,
  236. files: selectedImages,
  237. );
  238. messages = await MessagesRepository.getMessagesForThread(widget.conversation);
  239. setState(() {
  240. msgController.text = '';
  241. selectedImages = [];
  242. });
  243. },
  244. backgroundColor: Theme.of(context).primaryColor,
  245. child: Icon(
  246. Icons.send,
  247. color: Theme.of(context).colorScheme.onPrimary,
  248. size: 22
  249. ),
  250. ),
  251. ),
  252. ),
  253. const SizedBox(width: 10),
  254. ],
  255. ),
  256. AnimatedSwitcher(
  257. duration: const Duration(milliseconds: 250),
  258. transitionBuilder: (Widget child, Animation<double> animation) {
  259. return SizeTransition(sizeFactor: animation, child: child);
  260. },
  261. child: showFilePicker ?
  262. FilePicker(
  263. key: const Key('filePicker'),
  264. cameraHandle: (XFile image) {},
  265. galleryHandleMultiple: (List<XFile> images) async {
  266. for (var img in images) {
  267. selectedImages.add(File(img.path));
  268. }
  269. setState(() {
  270. showFilePicker = false;
  271. });
  272. },
  273. fileHandle: () {},
  274. ) :
  275. const SizedBox(height: 15),
  276. ),
  277. ],
  278. ),
  279. ),
  280. ),
  281. );
  282. }
  283. }