Browse Source

Add permissions view

feature/add-notifications
Tovi Jaeschke-Rogers 2 years ago
parent
commit
ad18b358ce
13 changed files with 281 additions and 45 deletions
  1. +9
    -3
      Backend/Api/Messages/CreateConversation.go
  2. +20
    -11
      Backend/Database/Seeder/MessageSeeder.go
  3. +3
    -0
      Backend/Models/Conversations.go
  4. +1
    -1
      Backend/Util/Files.go
  5. +0
    -0
      Backend/attachments/.gitkeep
  6. +3
    -3
      mobile/lib/components/select_message_ttl.dart
  7. +28
    -1
      mobile/lib/models/conversations.dart
  8. +0
    -1
      mobile/lib/utils/storage/conversations.dart
  9. +4
    -1
      mobile/lib/utils/storage/database.dart
  10. +25
    -19
      mobile/lib/views/main/conversation/edit_details.dart
  11. +174
    -0
      mobile/lib/views/main/conversation/permissions.dart
  12. +10
    -3
      mobile/lib/views/main/conversation/settings.dart
  13. +4
    -2
      mobile/lib/views/main/conversation/settings_user_list_item.dart

+ 9
- 3
Backend/Api/Messages/CreateConversation.go View File

@ -15,6 +15,9 @@ type RawCreateConversationData struct {
ID string `json:"id"`
Name string `json:"name"`
TwoUser string `json:"two_user"`
AdminAddMembers string `json:"admin_add_members"`
AdminEditInfo string `json:"admin_edit_info"`
AdminSendMessages string `json:"admin_send_messages"`
Users []Models.ConversationDetailUser `json:"users"`
UserConversations []Models.UserConversation `json:"user_conversations"`
}
@ -37,9 +40,12 @@ func CreateConversation(w http.ResponseWriter, r *http.Request) {
Base: Models.Base{
ID: uuid.FromStringOrNil(rawConversationData.ID),
},
Name: rawConversationData.Name,
TwoUser: rawConversationData.TwoUser,
Users: rawConversationData.Users,
Name: rawConversationData.Name,
TwoUser: rawConversationData.TwoUser,
AdminAddMembers: rawConversationData.AdminAddMembers,
AdminEditInfo: rawConversationData.AdminEditInfo,
AdminSendMessages: rawConversationData.AdminSendMessages,
Users: rawConversationData.Users,
}
err = Database.CreateConversationDetail(&messageThread)


+ 20
- 11
Backend/Database/Seeder/MessageSeeder.go View File

@ -93,11 +93,12 @@ func seedMessage(
func seedConversationDetail(key AesKey) (Models.ConversationDetail, error) {
var (
messageThread Models.ConversationDetail
name string
nameCiphertext []byte
twoUserCiphertext []byte
err error
conversationDetail Models.ConversationDetail
name string
nameCiphertext []byte
falseCiphertext []byte
trueCiphertext []byte
err error
)
name = "Test Conversation"
@ -107,18 +108,26 @@ func seedConversationDetail(key AesKey) (Models.ConversationDetail, error) {
panic(err)
}
twoUserCiphertext, err = key.AesEncrypt([]byte("false"))
falseCiphertext, err = key.AesEncrypt([]byte("false"))
if err != nil {
panic(err)
}
trueCiphertext, err = key.AesEncrypt([]byte("true"))
if err != nil {
panic(err)
}
messageThread = Models.ConversationDetail{
Name: base64.StdEncoding.EncodeToString(nameCiphertext),
TwoUser: base64.StdEncoding.EncodeToString(twoUserCiphertext),
conversationDetail = Models.ConversationDetail{
Name: base64.StdEncoding.EncodeToString(nameCiphertext),
TwoUser: base64.StdEncoding.EncodeToString(falseCiphertext),
AdminAddMembers: base64.StdEncoding.EncodeToString(trueCiphertext),
AdminEditInfo: base64.StdEncoding.EncodeToString(trueCiphertext),
AdminSendMessages: base64.StdEncoding.EncodeToString(falseCiphertext),
}
err = Database.CreateConversationDetail(&messageThread)
return messageThread, err
err = Database.CreateConversationDetail(&conversationDetail)
return conversationDetail, err
}
func seedUserConversation(


+ 3
- 0
Backend/Models/Conversations.go View File

@ -16,6 +16,9 @@ type ConversationDetail struct {
Attachment Attachment ` json:"attachment"`
MessageExpiryDefault MessageExpiry `gorm:"default:no_expiry" json:"-" sql:"type:ENUM('fifteen_min', 'thirty_min', 'one_hour', 'three_hour', 'six_hour', 'twelve_hour', 'one_day', 'three_day', 'no_expiry')"` // Stored encrypted
MessageExpiry string `gorm:"-" json:"message_expiry"` // Stored encrypted
AdminAddMembers string ` json:"admin_add_members"` // Stored encrypted
AdminEditInfo string ` json:"admin_edit_info"` // Stored encrypted
AdminSendMessages string ` json:"admin_send_messages"` // Stored encrypted
}
// ConversationDetailUser all users associated with a customer


+ 1
- 1
Backend/Util/Files.go View File

@ -17,7 +17,7 @@ func WriteFile(contents []byte) (string, error) {
fileName = RandomString(32)
filePath = fmt.Sprintf(
"/app/attachments/%s",
"./attachments/%s",
fileName,
)


+ 0
- 0
Backend/attachments/.gitkeep View File


+ 3
- 3
mobile/lib/components/select_message_ttl.dart View File

@ -58,12 +58,12 @@ class _SelectMessageTTLState extends State<SelectMessageTTL> {
),
body: Padding(
padding: const EdgeInsets.only(top: 30),
child: list(),
child: _list(),
),
);
}
Widget list() {
Widget _list() {
return ListView.builder(
itemCount: messageExpiryValues.length,
shrinkWrap: true,
@ -84,7 +84,7 @@ class _SelectMessageTTLState extends State<SelectMessageTTL> {
children: [
selectedExpiry == key ?
const Icon(Icons.check) :
const SizedBox(width: 20),
const SizedBox(width: 24),
const SizedBox(width: 16),
Expanded(
child: Align(


+ 28
- 1
mobile/lib/models/conversations.dart View File

@ -37,7 +37,10 @@ Future<Conversation> createConversation(String title, List<Friend> friends, bool
twoUser: twoUser,
status: ConversationStatus.pending,
isRead: true,
messageExpiryDefault: 'no_expiry'
messageExpiryDefault: 'no_expiry',
adminAddMembers: true,
adminEditInfo: true,
adminSendMessages: false,
);
await db.insert(
@ -163,6 +166,9 @@ Future<Conversation> getConversationById(String id) async {
isRead: maps[0]['is_read'] == 1,
icon: file,
messageExpiryDefault: maps[0]['message_expiry'],
adminAddMembers: maps[0]['admin_add_members'] == 1,
adminEditInfo: maps[0]['admin_edit_info'] == 1,
adminSendMessages: maps[0]['admin_send_messages'] == 1,
);
}
@ -193,6 +199,9 @@ Future<List<Conversation>> getConversations() async {
isRead: maps[i]['is_read'] == 1,
icon: file,
messageExpiryDefault: maps[i]['message_expiry'] ?? 'no_expiry',
adminAddMembers: maps[i]['admin_add_members'] == 1,
adminEditInfo: maps[i]['admin_edit_info'] == 1,
adminSendMessages: maps[i]['admin_send_messages'] == 1,
);
});
}
@ -227,6 +236,9 @@ Future<Conversation?> getTwoUserConversation(String userId) async {
status: ConversationStatus.values[maps[0]['status']],
isRead: maps[0]['is_read'] == 1,
messageExpiryDefault: maps[0]['message_expiry'],
adminAddMembers: maps[0]['admin_add_members'] == 1,
adminEditInfo: maps[0]['admin_edit_info'] == 1,
adminSendMessages: maps[0]['admin_send_messages'] == 1,
);
}
@ -241,6 +253,9 @@ class Conversation {
ConversationStatus status;
bool isRead;
String messageExpiryDefault = 'no_expiry';
bool adminAddMembers = true;
bool adminEditInfo = true;
bool adminSendMessages = false;
File? icon;
Conversation({
@ -253,6 +268,9 @@ class Conversation {
required this.status,
required this.isRead,
required this.messageExpiryDefault,
required this.adminAddMembers,
required this.adminEditInfo,
required this.adminSendMessages,
this.icon,
});
@ -283,6 +301,9 @@ class Conversation {
status: ConversationStatus.complete,
isRead: true,
messageExpiryDefault: 'no_expiry',
adminAddMembers: true,
adminEditInfo: true,
adminSendMessages: false,
);
}
@ -329,6 +350,9 @@ class Conversation {
'users': await getEncryptedConversationUsers(this, symKey),
'two_user': AesHelper.aesEncrypt(symKey, Uint8List.fromList((twoUser ? 'true' : 'false').codeUnits)),
'message_expiry': messageExpiryDefault,
'admin_add_members': AesHelper.aesEncrypt(symKey, Uint8List.fromList((adminAddMembers ? 'true' : 'false').codeUnits)),
'admin_edit_info': AesHelper.aesEncrypt(symKey, Uint8List.fromList((adminEditInfo ? 'true' : 'false').codeUnits)),
'admin_send_messages': AesHelper.aesEncrypt(symKey, Uint8List.fromList((adminSendMessages ? 'true' : 'false').codeUnits)),
'user_conversations': userConversations,
};
@ -367,6 +391,9 @@ class Conversation {
'is_read': isRead ? 1 : 0,
'file': icon != null ? icon!.path : null,
'message_expiry': messageExpiryDefault,
'admin_add_members': adminAddMembers ? 1 : 0,
'admin_edit_info': adminEditInfo ? 1 : 0,
'admin_send_messages': adminSendMessages ? 1 : 0,
};
}


+ 0
- 1
mobile/lib/utils/storage/conversations.dart View File

@ -3,7 +3,6 @@ import 'dart:convert';
import 'package:Capsule/exceptions/update_data_exception.dart';
import 'package:Capsule/utils/storage/get_file.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:http/http.dart' as http;
import 'package:pointycastle/export.dart';
import 'package:sqflite/sqflite.dart';


+ 4
- 1
mobile/lib/utils/storage/database.dart View File

@ -41,7 +41,10 @@ Future<Database> getDatabaseConnection() async {
status INTEGER,
is_read INTEGER,
file TEXT,
message_expiry TEXT
message_expiry TEXT,
admin_add_members INTEGER,
admin_edit_info INTEGER,
admin_send_messages INTEGER
);
''');


+ 25
- 19
mobile/lib/views/main/conversation/edit_details.dart View File

@ -125,25 +125,31 @@ class _ConversationEditDetails extends State<ConversationEditDetails> {
const SizedBox(height: 20),
showFileSelector ?
Padding(
padding: const EdgeInsets.only(bottom: 10),
child: FilePicker(
cameraHandle: (XFile image) {
setState(() {
conversationIcon = File(image.path);
showFileSelector = false;
});
},
galleryHandleSingle: (XFile image) async {
setState(() {
conversationIcon = File(image.path);
showFileSelector = false;
});
},
),
) :
const SizedBox(height: 10),
AnimatedSwitcher(
duration: const Duration(milliseconds: 250),
transitionBuilder: (Widget child, Animation<double> animation) {
return SizeTransition(sizeFactor: animation, child: child);
},
child: showFileSelector ?
Padding(
padding: const EdgeInsets.only(bottom: 10),
child: FilePicker(
cameraHandle: (XFile image) {
setState(() {
conversationIcon = File(image.path);
showFileSelector = false;
});
},
galleryHandleSingle: (XFile image) async {
setState(() {
conversationIcon = File(image.path);
showFileSelector = false;
});
},
),
) :
const SizedBox(height: 10),
),
TextFormField(
controller: conversationNameController,


+ 174
- 0
mobile/lib/views/main/conversation/permissions.dart View File

@ -0,0 +1,174 @@
import 'package:Capsule/components/flash_message.dart';
import 'package:Capsule/exceptions/update_data_exception.dart';
import 'package:Capsule/utils/storage/conversations.dart';
import 'package:Capsule/utils/storage/database.dart';
import 'package:flutter/material.dart';
import '/components/custom_title_bar.dart';
import '/models/conversations.dart';
class ConversationPermissions extends StatefulWidget {
const ConversationPermissions({
Key? key,
required this.conversation,
}) : super(key: key);
final Conversation conversation;
@override
_ConversationPermissionsState createState() => _ConversationPermissionsState();
}
class _ConversationPermissionsState extends State<ConversationPermissions> {
Map<String, Map<String, String>> perms = {
'admin_add_members': {
'title': 'Add Members',
'desc': 'Restrict adding members to admins',
},
'admin_edit_info': {
'title': 'Edit Info',
'desc': 'Restrict editing the conversation information to admins',
},
'admin_send_messages': {
'title': 'Send Messages',
'desc': 'Restrict sending messages to admins',
},
};
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomTitleBar(
title: const Text(
'Permissions',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold
)
),
beforeBack: () async {
final db = await getDatabaseConnection();
db.update(
'conversations',
widget.conversation.toMap(),
where: 'id = ?',
whereArgs: [widget.conversation.id],
);
updateConversation(widget.conversation)
.catchError((error) {
String message = error.toString();
if (error.runtimeType != UpdateDataException) {
message = 'An error occured, please try again later';
}
showMessage(message, context);
});
},
showBack: true,
backgroundColor: Colors.transparent,
),
body: Padding(
padding: const EdgeInsets.only(top: 30),
child: _list(),
),
);
}
Widget _list() {
return ListView.builder(
itemCount: perms.length,
shrinkWrap: true,
itemBuilder: (context, i) {
String key = perms.keys.elementAt(i);
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
_setValue(key);
},
child: Padding(
padding: const EdgeInsets.only(left: 30, right: 20, top: 8, bottom: 8),
child: Row(
children: [
_getValue(key) ?
const Icon(Icons.check) :
const SizedBox(width: 24),
const SizedBox(width: 16),
Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
perms[key]!['title'] ?? '',
textAlign: TextAlign.left,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.normal,
),
),
const SizedBox(height: 5),
Text(
perms[key]!['desc'] ?? '',
textAlign: TextAlign.left,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w200,
),
),
]
)
)
)
],
)
)
);
}
);
}
bool _getValue(String key) {
switch (key) {
case 'admin_add_members': {
return widget.conversation.adminAddMembers;
}
case 'admin_edit_info': {
return widget.conversation.adminEditInfo;
}
case 'admin_send_messages': {
return widget.conversation.adminSendMessages;
}
default: {
return false;
}
}
}
void _setValue(String key) {
switch (key) {
case 'admin_add_members': {
setState(() {
widget.conversation.adminAddMembers = !widget.conversation.adminAddMembers;
});
break;
}
case 'admin_edit_info': {
setState(() {
widget.conversation.adminEditInfo = !widget.conversation.adminEditInfo;
});
break;
}
case 'admin_send_messages': {
setState(() {
widget.conversation.adminSendMessages = !widget.conversation.adminSendMessages;
});
break;
}
}
}
}

+ 10
- 3
mobile/lib/views/main/conversation/settings.dart View File

@ -2,6 +2,7 @@ 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;
@ -23,12 +24,13 @@ import '/utils/storage/database.dart';
import '/utils/storage/conversations.dart';
class ConversationSettings extends StatefulWidget {
final Conversation conversation;
const ConversationSettings({
Key? key,
required this.conversation,
}) : super(key: key);
final Conversation conversation;
@override
State<ConversationSettings> createState() => _ConversationSettingsState();
}
@ -101,7 +103,7 @@ class _ConversationSettingsState extends State<ConversationSettings> {
),
),
widget.conversation.admin && !widget.conversation.twoUser ? IconButton(
(widget.conversation.admin && widget.conversation.adminEditInfo) && !widget.conversation.twoUser ? IconButton(
iconSize: 20,
icon: const Icon(Icons.edit),
padding: const EdgeInsets.all(5.0),
@ -303,7 +305,11 @@ return Theme.of(context).colorScheme.onBackground;
)
),
onPressed: () {
print('Permissions');
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => ConversationPermissions(
conversation: widget.conversation,
))
);
}
),
],
@ -321,6 +327,7 @@ return Theme.of(context).colorScheme.onBackground;
return ConversationSettingsUserListItem(
user: users[i],
isAdmin: widget.conversation.admin,
twoUser: widget.conversation.twoUser,
profile: profile!, // TODO: Fix this
);
}


+ 4
- 2
mobile/lib/views/main/conversation/settings_user_list_item.dart View File

@ -7,11 +7,13 @@ import '/models/my_profile.dart';
class ConversationSettingsUserListItem extends StatefulWidget{
final ConversationUser user;
final bool isAdmin;
final bool twoUser;
final MyProfile profile;
const ConversationSettingsUserListItem({
Key? key,
required this.user,
required this.isAdmin,
required this.twoUser,
required this.profile,
}) : super(key: key);
@ -22,7 +24,7 @@ class ConversationSettingsUserListItem extends StatefulWidget{
class _ConversationSettingsUserListItemState extends State<ConversationSettingsUserListItem> {
Widget admin() {
if (!widget.user.admin) {
if (!widget.user.admin || widget.twoUser) {
return const SizedBox.shrink();
}
@ -42,7 +44,7 @@ class _ConversationSettingsUserListItemState extends State<ConversationSettingsU
}
Widget adminUserActions() {
if (!widget.isAdmin || widget.user.admin || widget.user.username == widget.profile.username) {
if (!widget.isAdmin || widget.user.admin || widget.twoUser) {
return const SizedBox(height: 50);
}


Loading…
Cancel
Save