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.

292 lines
9.6 KiB

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