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.

296 lines
8.5 KiB

  1. import 'dart:io';
  2. import 'package:Envelope/models/image_message.dart';
  3. import 'package:Envelope/models/text_messages.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 '/models/conversations.dart';
  10. import '/models/messages.dart';
  11. import '/models/my_profile.dart';
  12. import '/utils/storage/messages.dart';
  13. import '/utils/time.dart';
  14. import '/views/main/conversation/settings.dart';
  15. class ConversationDetail extends StatefulWidget{
  16. final Conversation conversation;
  17. const ConversationDetail({
  18. Key? key,
  19. required this.conversation,
  20. }) : super(key: key);
  21. @override
  22. _ConversationDetailState createState() => _ConversationDetailState();
  23. }
  24. class _ConversationDetailState extends State<ConversationDetail> {
  25. List<Message> messages = [];
  26. MyProfile profile = MyProfile(
  27. id: '',
  28. username: '',
  29. messageExpiryDefault: 'no_expiry',
  30. );
  31. TextEditingController msgController = TextEditingController();
  32. bool showFilePicker = false;
  33. List<File> selectedImages = [];
  34. @override
  35. Widget build(BuildContext context) {
  36. return Scaffold(
  37. appBar: CustomTitleBar(
  38. title: Text(
  39. widget.conversation.name,
  40. style: TextStyle(
  41. fontSize: 16,
  42. fontWeight: FontWeight.w600,
  43. color: Theme.of(context).appBarTheme.toolbarTextStyle?.color
  44. ),
  45. ),
  46. showBack: true,
  47. rightHandButton: IconButton(
  48. onPressed: (){
  49. Navigator.of(context).push(
  50. MaterialPageRoute(builder: (context) => ConversationSettings(
  51. conversation: widget.conversation
  52. )),
  53. );
  54. },
  55. icon: Icon(
  56. Icons.settings,
  57. color: Theme.of(context).appBarTheme.iconTheme?.color,
  58. ),
  59. ),
  60. ),
  61. body: Stack(
  62. children: <Widget>[
  63. messagesView(),
  64. newMessageContent(),
  65. ],
  66. ),
  67. );
  68. }
  69. Future<void> fetchMessages() async {
  70. profile = await MyProfile.getProfile();
  71. messages = await getMessagesForThread(widget.conversation);
  72. setState(() {});
  73. }
  74. @override
  75. void initState() {
  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. showFilePicker = !showFilePicker;
  187. });
  188. },
  189. child: Container(
  190. height: 30,
  191. width: 30,
  192. decoration: BoxDecoration(
  193. color: Theme.of(context).primaryColor,
  194. borderRadius: BorderRadius.circular(30),
  195. ),
  196. child: Icon(
  197. Icons.add,
  198. color: Theme.of(context).colorScheme.onPrimary,
  199. size: 20
  200. ),
  201. ),
  202. ),
  203. const SizedBox(width: 15,),
  204. Expanded(
  205. child: TextField(
  206. decoration: InputDecoration(
  207. hintText: 'Write message...',
  208. hintStyle: TextStyle(
  209. color: Theme.of(context).hintColor,
  210. ),
  211. border: InputBorder.none,
  212. ),
  213. maxLines: null,
  214. controller: msgController,
  215. ),
  216. ),
  217. const SizedBox(width: 15),
  218. SizedBox(
  219. width: 45,
  220. height: 45,
  221. child: FittedBox(
  222. child: FloatingActionButton(
  223. onPressed: () async {
  224. if (msgController.text == '' && selectedImages.isEmpty) {
  225. return;
  226. }
  227. await sendMessage(
  228. widget.conversation,
  229. data: msgController.text != '' ? msgController.text : null,
  230. files: selectedImages,
  231. );
  232. messages = await getMessagesForThread(widget.conversation);
  233. setState(() {});
  234. msgController.text = '';
  235. },
  236. child: Icon(
  237. Icons.send,
  238. color: Theme.of(context).colorScheme.onPrimary,
  239. size: 22
  240. ),
  241. backgroundColor: Theme.of(context).primaryColor,
  242. ),
  243. ),
  244. ),
  245. const SizedBox(width: 10),
  246. ],
  247. ),
  248. showFilePicker ?
  249. FilePicker(
  250. cameraHandle: () {},
  251. galleryHandleMultiple: (List<XFile> images) async {
  252. for (var img in images) {
  253. selectedImages.add(File(img.path));
  254. }
  255. setState(() {
  256. showFilePicker = false;
  257. });
  258. },
  259. fileHandle: () {},
  260. ) :
  261. const SizedBox.shrink(),
  262. ],
  263. ),
  264. ),
  265. ),
  266. );
  267. }
  268. }