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.

301 lines
8.7 KiB

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