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.

312 lines
9.1 KiB

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