import 'dart:convert'; import 'dart:io'; import 'package:Envelope/database/repositories/conversation_users_repository.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import '/components/custom_title_bar.dart'; import '/components/flash_message.dart'; import '/components/select_message_ttl.dart'; import '/exceptions/update_data_exception.dart'; import '/utils/encryption/crypto_utils.dart'; import '/utils/storage/write_file.dart'; import '/views/main/conversation/create_add_users.dart'; import '/database/models/friends.dart'; import '/database/models/conversation_users.dart'; import '/database/models/conversations.dart'; import '/database/models/my_profile.dart'; import '/views/main/conversation/settings_user_list_item.dart'; import '/views/main/conversation/edit_details.dart'; import '/components/custom_circle_avatar.dart'; import '/utils/storage/database.dart'; import '/services/conversations_service.dart'; import '/utils/storage/session_cookie.dart'; import '/views/main/conversation/permissions.dart'; class ConversationSettings extends StatefulWidget { const ConversationSettings({ Key? key, required this.conversation, }) : super(key: key); final Conversation conversation; @override State createState() => _ConversationSettingsState(); } class _ConversationSettingsState extends State { List users = []; MyProfile? profile; TextEditingController nameController = TextEditingController(); @override Widget build(BuildContext context) { return Scaffold( appBar: CustomTitleBar( title: Text( '${widget.conversation.name} Settings', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Theme.of(context).appBarTheme.toolbarTextStyle?.color ), ), showBack: true, ), body: Padding( padding: const EdgeInsets.all(15), child: SingleChildScrollView( child: Column( children: [ const SizedBox(height: 30), conversationName(), const SizedBox(height: 25), widget.conversation.admin ? sectionTitle('Settings') : const SizedBox.shrink(), widget.conversation.admin ? settings() : const SizedBox.shrink(), widget.conversation.admin ? const SizedBox(height: 25) : const SizedBox.shrink(), sectionTitle('Members', showUsersAdd: widget.conversation.admin && !widget.conversation.twoUser), usersList(), const SizedBox(height: 25), myAccess(), ], ), ), ), ); } Widget conversationName() { return Row( children: [ CustomCircleAvatar( icon: const Icon(Icons.people, size: 40), radius: 30, image: widget.conversation.icon, ), const SizedBox(width: 10), Text( widget.conversation.name, style: const TextStyle( fontSize: 25, fontWeight: FontWeight.w500, ), ), (widget.conversation.admin && widget.conversation.adminEditInfo) && !widget.conversation.twoUser ? IconButton( iconSize: 20, icon: const Icon(Icons.edit), padding: const EdgeInsets.all(5.0), splashRadius: 25, onPressed: () { Navigator.of(context).push( MaterialPageRoute(builder: (context) => ConversationEditDetails( // TODO: Move saveCallback to somewhere else saveCallback: (String conversationName, File? file) async { bool updatedImage = false; File? writtenFile; if (file != null) { updatedImage = file.hashCode != widget.conversation.icon.hashCode; writtenFile = await writeImage( widget.conversation.id, file.readAsBytesSync(), ); } widget.conversation.name = conversationName; widget.conversation.icon = writtenFile; await ConversationsService.updateConversation(widget.conversation, updatedImage: updatedImage) .catchError((error) { String message = error.toString(); if (error.runtimeType != UpdateDataException) { message = 'An error occured, please try again later'; } showMessage(message, context); }); setState(() {}); if (!mounted) { return; } Navigator.pop(context); }, conversation: widget.conversation, )), ).then(onGoBack); }, ) : const SizedBox.shrink(), ], ); } Future getUsers() async { users = await ConversationUsersRepository.getConversationUsers(widget.conversation); profile = await MyProfile.getProfile(); setState(() {}); } @override void initState() { nameController.text = widget.conversation.name; super.initState(); getUsers(); } Widget myAccess() { return Align( alignment: Alignment.centerLeft, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ TextButton.icon( label: const Text( 'Leave Conversation', style: TextStyle(fontSize: 16) ), icon: const Icon(Icons.exit_to_app), style: ButtonStyle( foregroundColor: MaterialStateProperty.all(Theme.of(context).colorScheme.error), alignment: Alignment.centerLeft, ), onPressed: () { print('Leave Group'); } ), ], ), ); } Widget sectionTitle(String title, { bool showUsersAdd = false}) { return Align( alignment: Alignment.centerLeft, child: Padding( padding: const EdgeInsets.only(right: 6), child: Row( children: [ Expanded( child: Container( padding: const EdgeInsets.only(left: 12), child: Text( title, style: const TextStyle(fontSize: 20), ), ), ), !showUsersAdd ? const SizedBox.shrink() : IconButton( icon: const Icon(Icons.add), padding: const EdgeInsets.all(0), onPressed: () async { List friends = await unselectedFriends(); if (!mounted) { return; } Navigator.of(context).push( MaterialPageRoute(builder: (context) => ConversationAddFriendsList( friends: friends, saveCallback: (List selectedFriends) async { ConversationsService.addUsersToConversation( widget.conversation, selectedFriends, ); await ConversationsService.updateConversation(widget.conversation, includeUsers: true); await getUsers(); if (!mounted) { return; } Navigator.pop(context); }, )) ); }, ), ], ) ) ); } Widget settings() { return Align( alignment: Alignment.centerLeft, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const SizedBox(height: 5), TextButton.icon( label: const Text( 'Disappearing Messages', style: TextStyle(fontSize: 16) ), icon: const Icon(Icons.timer), style: ButtonStyle( alignment: Alignment.centerLeft, foregroundColor: MaterialStateProperty.resolveWith( (Set states) { return Theme.of(context).colorScheme.onBackground; }, ) ), onPressed: () { Navigator.of(context).push( MaterialPageRoute(builder: (context) => SelectMessageTTL( widgetTitle: 'Message Expiry', currentSelected: widget.conversation.messageExpiryDefault, backCallback: (String messageExpiry) async { widget.conversation.messageExpiryDefault = messageExpiry; ConversationsService.updateMessageExpiry(widget.conversation.id, messageExpiry) .catchError((dynamic) { showMessage( 'Could not change the default message expiry, please try again later.', context, ); }); ConversationsService.saveConversation(widget.conversation); } )) ); } ), TextButton.icon( label: const Text( 'Permissions', style: TextStyle(fontSize: 16) ), icon: const Icon(Icons.lock), style: ButtonStyle( alignment: Alignment.centerLeft, foregroundColor: MaterialStateProperty.resolveWith( (Set states) { return Theme.of(context).colorScheme.onBackground; }, ) ), onPressed: () { Navigator.of(context).push( MaterialPageRoute(builder: (context) => ConversationPermissions( conversation: widget.conversation, )) ); } ), ], ), ); } Widget usersList() { return ListView.builder( itemCount: users.length, shrinkWrap: true, padding: const EdgeInsets.only(top: 5, bottom: 0), physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, i) { return ConversationSettingsUserListItem( user: users[i], isAdmin: widget.conversation.admin, twoUser: widget.conversation.twoUser, profile: profile!, ); } ); } Future> unselectedFriends() async { final db = await getDatabaseConnection(); List notInArgs = []; for (var user in users) { notInArgs.add(user.userId); } final List> maps = await db.query( 'friends', where: 'friend_id not in (${List.filled(notInArgs.length, '?').join(',')})', whereArgs: notInArgs, orderBy: 'username', ); return List.generate(maps.length, (i) { return Friend( id: maps[i]['id'], userId: maps[i]['user_id'], friendId: maps[i]['friend_id'], friendSymmetricKey: maps[i]['symmetric_key'], publicKey: CryptoUtils.rsaPublicKeyFromPem(maps[i]['asymmetric_public_key']), acceptedAt: maps[i]['accepted_at'], username: maps[i]['username'], ); }); } onGoBack(dynamic value) async { nameController.text = widget.conversation.name; getUsers(); setState(() {}); } }