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.

381 lines
12 KiB

import 'dart:convert';
import 'dart:io';
import 'package:Capsule/utils/storage/session_cookie.dart';
import 'package:Capsule/views/main/conversation/permissions.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 '/models/friends.dart';
import '/utils/encryption/crypto_utils.dart';
import '/utils/storage/write_file.dart';
import '/views/main/conversation/create_add_users.dart';
import '/models/conversation_users.dart';
import '/models/conversations.dart';
import '/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 '/utils/storage/conversations.dart';
class ConversationSettings extends StatefulWidget {
const ConversationSettings({
Key? key,
required this.conversation,
}) : super(key: key);
final Conversation conversation;
@override
State<ConversationSettings> createState() => _ConversationSettingsState();
}
class _ConversationSettingsState extends State<ConversationSettings> {
List<ConversationUser> 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: <Widget> [
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: <Widget> [
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 saveConversation();
await 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(() {});
Navigator.pop(context);
},
conversation: widget.conversation,
)),
).then(onGoBack);
},
) : const SizedBox.shrink(),
],
);
}
Future<void> getUsers() async {
users = await 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: const ButtonStyle(
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<Friend> friends = await unselectedFriends();
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => ConversationAddFriendsList(
friends: friends,
saveCallback: (List<Friend> selectedFriends) async {
addUsersToConversation(
widget.conversation,
selectedFriends,
);
await updateConversation(widget.conversation, includeUsers: true);
await getUsers();
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<Color>(
(Set<MaterialState> 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;
http.post(
await MyProfile.getServerUrl(
'api/v1/auth/conversations/${widget.conversation.id}/message_expiry'
),
headers: {
'cookie': await getSessionCookie(),
},
body: jsonEncode({
'message_expiry': messageExpiry,
}),
).then((http.Response response) {
if (response.statusCode == 204) {
return;
}
showMessage(
'Could not change the default message expiry, please try again later.',
context,
);
});
saveConversation();
}
))
);
}
),
TextButton.icon(
label: const Text(
'Permissions',
style: TextStyle(fontSize: 16)
),
icon: const Icon(Icons.lock),
style: ButtonStyle(
alignment: Alignment.centerLeft,
foregroundColor: MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> 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!, // TODO: Fix this
);
}
);
}
Future<List<Friend>> unselectedFriends() async {
final db = await getDatabaseConnection();
List<String> notInArgs = [];
for (var user in users) {
notInArgs.add(user.userId);
}
final List<Map<String, dynamic>> 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(() {});
}
saveConversation() async {
final db = await getDatabaseConnection();
db.update(
'conversations',
widget.conversation.toMap(),
where: 'id = ?',
whereArgs: [widget.conversation.id],
);
}
}