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.

314 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. print(widget.conversation.admin);
  75. print(widget.conversation.adminSendMessages);
  76. sendDisabled = widget.conversation.adminSendMessages && !widget.conversation.admin;
  77. super.initState();
  78. fetchMessages();
  79. }
  80. Widget messagesView() {
  81. if (messages.isEmpty) {
  82. return const Center(
  83. child: Text('No Messages'),
  84. );
  85. }
  86. return ListView.builder(
  87. itemCount: messages.length,
  88. shrinkWrap: true,
  89. padding: EdgeInsets.only(
  90. top: 10,
  91. bottom: selectedImages.isEmpty ? 90 : 160,
  92. ),
  93. reverse: true,
  94. itemBuilder: (context, index) {
  95. return ConversationMessage(
  96. message: messages[index],
  97. profile: profile,
  98. index: index,
  99. );
  100. },
  101. );
  102. }
  103. Widget showSelectedImages() {
  104. if (selectedImages.isEmpty) {
  105. return const SizedBox.shrink();
  106. }
  107. return SizedBox(
  108. height: 80,
  109. width: double.infinity,
  110. child: ListView.builder(
  111. itemCount: selectedImages.length,
  112. shrinkWrap: true,
  113. scrollDirection: Axis.horizontal,
  114. padding: const EdgeInsets.all(5),
  115. itemBuilder: (context, i) {
  116. return Stack(
  117. children: [
  118. Column(
  119. children: [
  120. const SizedBox(height: 5),
  121. Container(
  122. alignment: Alignment.center,
  123. height: 65,
  124. width: 65,
  125. child: Image.file(
  126. selectedImages[i],
  127. fit: BoxFit.fill,
  128. ),
  129. ),
  130. ],
  131. ),
  132. SizedBox(
  133. height: 60,
  134. width: 70,
  135. child: Align(
  136. alignment: Alignment.topRight,
  137. child: GestureDetector(
  138. onTap: () {
  139. setState(() {
  140. selectedImages.removeAt(i);
  141. });
  142. },
  143. child: Container(
  144. height: 20,
  145. width: 20,
  146. decoration: BoxDecoration(
  147. color: Theme.of(context).colorScheme.onPrimary,
  148. borderRadius: BorderRadius.circular(30),
  149. ),
  150. child: Icon(
  151. Icons.cancel,
  152. color: Theme.of(context).primaryColor,
  153. size: 20
  154. ),
  155. ),
  156. ),
  157. ),
  158. ),
  159. ],
  160. );
  161. },
  162. )
  163. );
  164. }
  165. Widget newMessageContent() {
  166. return Align(
  167. alignment: Alignment.bottomLeft,
  168. child: ConstrainedBox(
  169. constraints: BoxConstraints(
  170. maxHeight: selectedImages.isEmpty ?
  171. 200.0 :
  172. 270.0,
  173. ),
  174. child: Container(
  175. padding: const EdgeInsets.only(left: 10,bottom: 10,top: 10),
  176. width: double.infinity,
  177. color: Theme.of(context).backgroundColor,
  178. child: Column(
  179. mainAxisSize: MainAxisSize.min,
  180. children: [
  181. showSelectedImages(),
  182. Row(
  183. children: <Widget>[
  184. GestureDetector(
  185. onTap: (){
  186. setState(() {
  187. if (sendDisabled) {
  188. return;
  189. }
  190. showFilePicker = !showFilePicker;
  191. });
  192. },
  193. child: Container(
  194. height: 30,
  195. width: 30,
  196. decoration: BoxDecoration(
  197. color: Theme.of(context).primaryColor,
  198. borderRadius: BorderRadius.circular(30),
  199. ),
  200. child: Icon(
  201. Icons.add,
  202. color: Theme.of(context).colorScheme.onPrimary,
  203. size: 20
  204. ),
  205. ),
  206. ),
  207. const SizedBox(width: 15,),
  208. Expanded(
  209. child: TextField(
  210. enabled: !sendDisabled,
  211. decoration: InputDecoration(
  212. hintText: sendDisabled ?
  213. 'Messages disabled for non-admins' :
  214. 'Write message...',
  215. hintStyle: TextStyle(
  216. color: Theme.of(context).hintColor,
  217. ),
  218. border: InputBorder.none,
  219. ),
  220. maxLines: null,
  221. controller: msgController,
  222. ),
  223. ),
  224. const SizedBox(width: 15),
  225. SizedBox(
  226. width: 45,
  227. height: 45,
  228. child: FittedBox(
  229. child: FloatingActionButton(
  230. onPressed: () async {
  231. if ((msgController.text == '' && selectedImages.isEmpty) || sendDisabled) {
  232. return;
  233. }
  234. await sendMessage(
  235. widget.conversation,
  236. data: msgController.text != '' ? msgController.text : null,
  237. files: selectedImages,
  238. );
  239. messages = await getMessagesForThread(widget.conversation);
  240. setState(() {
  241. msgController.text = '';
  242. selectedImages = [];
  243. });
  244. },
  245. child: Icon(
  246. Icons.send,
  247. color: Theme.of(context).colorScheme.onPrimary,
  248. size: 22
  249. ),
  250. backgroundColor: Theme.of(context).primaryColor,
  251. ),
  252. ),
  253. ),
  254. const SizedBox(width: 10),
  255. ],
  256. ),
  257. AnimatedSwitcher(
  258. duration: const Duration(milliseconds: 250),
  259. transitionBuilder: (Widget child, Animation<double> animation) {
  260. return SizeTransition(sizeFactor: animation, child: child);
  261. },
  262. child: showFilePicker ?
  263. FilePicker(
  264. key: const Key('filePicker'),
  265. cameraHandle: (XFile image) {},
  266. galleryHandleMultiple: (List<XFile> images) async {
  267. for (var img in images) {
  268. selectedImages.add(File(img.path));
  269. }
  270. setState(() {
  271. showFilePicker = false;
  272. });
  273. },
  274. fileHandle: () {},
  275. ) :
  276. const SizedBox(height: 15),
  277. ),
  278. ],
  279. ),
  280. ),
  281. ),
  282. );
  283. }
  284. }