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.

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