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.

321 lines
9.4 KiB

  1. import 'dart:io';
  2. import 'package:Envelope/components/custom_title_bar.dart';
  3. import 'package:Envelope/models/friends.dart';
  4. import 'package:Envelope/utils/encryption/crypto_utils.dart';
  5. import 'package:Envelope/utils/storage/write_file.dart';
  6. import 'package:Envelope/views/main/conversation/create_add_users.dart';
  7. import 'package:flutter/material.dart';
  8. import '/models/conversation_users.dart';
  9. import '/models/conversations.dart';
  10. import '/models/my_profile.dart';
  11. import '/views/main/conversation/settings_user_list_item.dart';
  12. import '/views/main/conversation/edit_details.dart';
  13. import '/components/custom_circle_avatar.dart';
  14. import '/utils/storage/database.dart';
  15. import '/utils/storage/conversations.dart';
  16. class ConversationSettings extends StatefulWidget {
  17. final Conversation conversation;
  18. const ConversationSettings({
  19. Key? key,
  20. required this.conversation,
  21. }) : super(key: key);
  22. @override
  23. State<ConversationSettings> createState() => _ConversationSettingsState();
  24. }
  25. class _ConversationSettingsState extends State<ConversationSettings> {
  26. List<ConversationUser> users = [];
  27. MyProfile? profile;
  28. TextEditingController nameController = TextEditingController();
  29. @override
  30. Widget build(BuildContext context) {
  31. return Scaffold(
  32. appBar: CustomTitleBar(
  33. title: Text(
  34. widget.conversation.name + ' Settings',
  35. style: TextStyle(
  36. fontSize: 16,
  37. fontWeight: FontWeight.w600,
  38. color: Theme.of(context).appBarTheme.toolbarTextStyle?.color
  39. ),
  40. ),
  41. showBack: true,
  42. ),
  43. body: Padding(
  44. padding: const EdgeInsets.all(15),
  45. child: SingleChildScrollView(
  46. child: Column(
  47. children: <Widget> [
  48. const SizedBox(height: 30),
  49. conversationName(),
  50. const SizedBox(height: 25),
  51. widget.conversation.admin ?
  52. sectionTitle('Settings') :
  53. const SizedBox.shrink(),
  54. widget.conversation.admin ?
  55. settings() :
  56. const SizedBox.shrink(),
  57. widget.conversation.admin ?
  58. const SizedBox(height: 25) :
  59. const SizedBox.shrink(),
  60. sectionTitle('Members', showUsersAdd: widget.conversation.admin && !widget.conversation.twoUser),
  61. usersList(),
  62. const SizedBox(height: 25),
  63. myAccess(),
  64. ],
  65. ),
  66. ),
  67. ),
  68. );
  69. }
  70. Widget conversationName() {
  71. return Row(
  72. children: <Widget> [
  73. CustomCircleAvatar(
  74. icon: const Icon(Icons.people, size: 40),
  75. radius: 30,
  76. image: widget.conversation.icon,
  77. ),
  78. const SizedBox(width: 10),
  79. Text(
  80. widget.conversation.name,
  81. style: const TextStyle(
  82. fontSize: 25,
  83. fontWeight: FontWeight.w500,
  84. ),
  85. ),
  86. widget.conversation.admin && !widget.conversation.twoUser ? IconButton(
  87. iconSize: 20,
  88. icon: const Icon(Icons.edit),
  89. padding: const EdgeInsets.all(5.0),
  90. splashRadius: 25,
  91. onPressed: () {
  92. Navigator.of(context).push(
  93. MaterialPageRoute(builder: (context) => ConversationEditDetails(
  94. saveCallback: (String conversationName, File? file) async {
  95. File? writtenFile;
  96. if (file != null) {
  97. writtenFile = await writeImage(
  98. widget.conversation.id,
  99. file.readAsBytesSync(),
  100. );
  101. }
  102. widget.conversation.name = conversationName;
  103. widget.conversation.icon = writtenFile;
  104. final db = await getDatabaseConnection();
  105. db.update(
  106. 'conversations',
  107. widget.conversation.toMap(),
  108. where: 'id = ?',
  109. whereArgs: [widget.conversation.id],
  110. );
  111. await updateConversation(widget.conversation, includeUsers: true);
  112. setState(() {});
  113. Navigator.pop(context);
  114. },
  115. conversation: widget.conversation,
  116. )),
  117. ).then(onGoBack);
  118. },
  119. ) : const SizedBox.shrink(),
  120. ],
  121. );
  122. }
  123. Future<void> getUsers() async {
  124. users = await getConversationUsers(widget.conversation);
  125. profile = await MyProfile.getProfile();
  126. setState(() {});
  127. }
  128. @override
  129. void initState() {
  130. nameController.text = widget.conversation.name;
  131. super.initState();
  132. getUsers();
  133. }
  134. Widget myAccess() {
  135. return Align(
  136. alignment: Alignment.centerLeft,
  137. child: Column(
  138. crossAxisAlignment: CrossAxisAlignment.stretch,
  139. children: [
  140. TextButton.icon(
  141. label: const Text(
  142. 'Leave Conversation',
  143. style: TextStyle(fontSize: 16)
  144. ),
  145. icon: const Icon(Icons.exit_to_app),
  146. style: const ButtonStyle(
  147. alignment: Alignment.centerLeft,
  148. ),
  149. onPressed: () {
  150. print('Leave Group');
  151. }
  152. ),
  153. ],
  154. ),
  155. );
  156. }
  157. Widget sectionTitle(String title, { bool showUsersAdd = false}) {
  158. return Align(
  159. alignment: Alignment.centerLeft,
  160. child: Padding(
  161. padding: const EdgeInsets.only(right: 6),
  162. child: Row(
  163. children: [
  164. Expanded(
  165. child: Container(
  166. padding: const EdgeInsets.only(left: 12),
  167. child: Text(
  168. title,
  169. style: const TextStyle(fontSize: 20),
  170. ),
  171. ),
  172. ),
  173. !showUsersAdd ?
  174. const SizedBox.shrink() :
  175. IconButton(
  176. icon: const Icon(Icons.add),
  177. padding: const EdgeInsets.all(0),
  178. onPressed: () async {
  179. List<Friend> friends = await unselectedFriends();
  180. Navigator.of(context).push(
  181. MaterialPageRoute(builder: (context) => ConversationAddFriendsList(
  182. friends: friends,
  183. saveCallback: (List<Friend> selectedFriends) async {
  184. addUsersToConversation(
  185. widget.conversation,
  186. selectedFriends,
  187. );
  188. await updateConversation(widget.conversation, includeUsers: true);
  189. await getUsers();
  190. Navigator.pop(context);
  191. },
  192. ))
  193. );
  194. },
  195. ),
  196. ],
  197. )
  198. )
  199. );
  200. }
  201. Widget settings() {
  202. return Align(
  203. alignment: Alignment.centerLeft,
  204. child: Column(
  205. crossAxisAlignment: CrossAxisAlignment.stretch,
  206. children: [
  207. const SizedBox(height: 5),
  208. TextButton.icon(
  209. label: const Text(
  210. 'Disappearing Messages',
  211. style: TextStyle(fontSize: 16)
  212. ),
  213. icon: const Icon(Icons.timer),
  214. style: ButtonStyle(
  215. alignment: Alignment.centerLeft,
  216. foregroundColor: MaterialStateProperty.resolveWith<Color>(
  217. (Set<MaterialState> states) {
  218. return Theme.of(context).colorScheme.onBackground;
  219. },
  220. )
  221. ),
  222. onPressed: () {
  223. print('Disappearing Messages');
  224. }
  225. ),
  226. TextButton.icon(
  227. label: const Text(
  228. 'Permissions',
  229. style: TextStyle(fontSize: 16)
  230. ),
  231. icon: const Icon(Icons.lock),
  232. style: ButtonStyle(
  233. alignment: Alignment.centerLeft,
  234. foregroundColor: MaterialStateProperty.resolveWith<Color>(
  235. (Set<MaterialState> states) {
  236. return Theme.of(context).colorScheme.onBackground;
  237. },
  238. )
  239. ),
  240. onPressed: () {
  241. print('Permissions');
  242. }
  243. ),
  244. ],
  245. ),
  246. );
  247. }
  248. Widget usersList() {
  249. return ListView.builder(
  250. itemCount: users.length,
  251. shrinkWrap: true,
  252. padding: const EdgeInsets.only(top: 5, bottom: 0),
  253. physics: const NeverScrollableScrollPhysics(),
  254. itemBuilder: (context, i) {
  255. return ConversationSettingsUserListItem(
  256. user: users[i],
  257. isAdmin: widget.conversation.admin,
  258. profile: profile!, // TODO: Fix this
  259. );
  260. }
  261. );
  262. }
  263. Future<List<Friend>> unselectedFriends() async {
  264. final db = await getDatabaseConnection();
  265. List<String> notInArgs = [];
  266. for (var user in users) {
  267. notInArgs.add(user.userId);
  268. }
  269. final List<Map<String, dynamic>> maps = await db.query(
  270. 'friends',
  271. where: 'friend_id not in (${List.filled(notInArgs.length, '?').join(',')})',
  272. whereArgs: notInArgs,
  273. orderBy: 'username',
  274. );
  275. return List.generate(maps.length, (i) {
  276. return Friend(
  277. id: maps[i]['id'],
  278. userId: maps[i]['user_id'],
  279. friendId: maps[i]['friend_id'],
  280. friendSymmetricKey: maps[i]['symmetric_key'],
  281. publicKey: CryptoUtils.rsaPublicKeyFromPem(maps[i]['asymmetric_public_key']),
  282. acceptedAt: maps[i]['accepted_at'],
  283. username: maps[i]['username'],
  284. );
  285. });
  286. }
  287. onGoBack(dynamic value) async {
  288. nameController.text = widget.conversation.name;
  289. getUsers();
  290. setState(() {});
  291. }
  292. }