1
0
mirror of https://gitlab.com/comunic/comunicmobile synced 2024-11-22 21:09:21 +00:00
comunicmobile/lib/ui/screens/update_conversation_screen.dart

409 lines
13 KiB
Dart
Raw Normal View History

2021-03-13 10:33:25 +00:00
import 'package:cached_network_image/cached_network_image.dart';
2021-03-13 09:32:11 +00:00
import 'package:comunic/helpers/conversations_helper.dart';
2021-03-13 10:33:25 +00:00
import 'package:comunic/helpers/server_config_helper.dart';
2021-03-13 09:32:11 +00:00
import 'package:comunic/helpers/users_helper.dart';
2019-04-27 14:23:08 +00:00
import 'package:comunic/lists/users_list.dart';
2019-05-01 07:24:50 +00:00
import 'package:comunic/models/conversation.dart';
2021-03-13 09:32:11 +00:00
import 'package:comunic/models/new_conversation.dart';
2021-03-13 10:08:08 +00:00
import 'package:comunic/models/new_conversation_settings.dart';
2020-04-26 13:25:56 +00:00
import 'package:comunic/models/user.dart';
2021-03-13 09:09:17 +00:00
import 'package:comunic/ui/dialogs/color_picker_dialog.dart';
2021-03-13 10:33:25 +00:00
import 'package:comunic/ui/dialogs/pick_file_dialog.dart';
2021-03-13 09:32:11 +00:00
import 'package:comunic/ui/routes/main_route/main_route.dart';
2019-04-27 14:23:08 +00:00
import 'package:comunic/ui/tiles/simple_user_tile.dart';
2021-03-13 09:32:11 +00:00
import 'package:comunic/ui/widgets/async_screen_widget.dart';
import 'package:comunic/ui/widgets/comunic_back_button_widget.dart';
2019-04-27 14:23:08 +00:00
import 'package:comunic/ui/widgets/pick_user_widget.dart';
2021-03-13 09:48:59 +00:00
import 'package:comunic/utils/account_utils.dart';
2021-03-13 09:09:17 +00:00
import 'package:comunic/utils/color_utils.dart';
import 'package:comunic/utils/dart_color.dart';
2019-04-27 14:23:08 +00:00
import 'package:comunic/utils/intl_utils.dart';
2021-03-13 09:09:17 +00:00
import 'package:comunic/utils/log_utils.dart';
2021-03-13 09:32:11 +00:00
import 'package:comunic/utils/ui_utils.dart';
2019-04-27 14:23:08 +00:00
import 'package:flutter/material.dart';
/// Create / Update conversation screen
///
/// @author Pierre HUBERT
2021-03-13 09:48:59 +00:00
enum _MembersMenuChoices { TOGGLE_ADMIN_STATUS, REMOVE }
2019-04-27 14:23:08 +00:00
class UpdateConversationScreen extends StatefulWidget {
2021-03-13 09:32:11 +00:00
final convID;
2019-05-01 07:24:50 +00:00
const UpdateConversationScreen({
Key? key,
2021-03-13 09:32:11 +00:00
this.convID,
2019-05-01 07:24:50 +00:00
}) : super(key: key);
2019-04-27 14:23:08 +00:00
@override
State<StatefulWidget> createState() => _UpdateConversationScreen();
}
class _UpdateConversationScreen extends State<UpdateConversationScreen> {
late Conversation _conversation;
2021-03-13 09:32:11 +00:00
2019-04-27 14:23:08 +00:00
TextEditingController _nameController = TextEditingController();
2021-03-13 09:09:17 +00:00
TextEditingController _colorController = TextEditingController();
2019-04-27 14:23:08 +00:00
UsersList _members = UsersList();
Set<int?> _admins = Set();
2019-04-27 14:23:08 +00:00
bool _followConversation = true;
bool? _canEveryoneAddMembers = true;
String? _image;
2019-04-27 14:23:08 +00:00
2021-03-13 09:09:17 +00:00
String get _conversationColor => _colorController.text;
Color? get _color {
2022-03-11 15:40:56 +00:00
if (_conversationColor.isEmpty) return null;
2021-03-13 09:09:17 +00:00
try {
return HexColor(_conversationColor);
} catch (e, s) {
logError(e, s);
return null;
}
}
2021-03-13 09:32:11 +00:00
get isUpdating => widget.convID != null;
2019-05-01 07:24:50 +00:00
2021-03-13 09:32:11 +00:00
get isAdmin => !isUpdating || _conversation.isAdmin;
2019-05-01 07:24:50 +00:00
2021-04-06 16:41:51 +00:00
bool get _isGroupConversation =>
isUpdating && _conversation.isGroupConversation;
2021-03-13 10:02:44 +00:00
bool get _canAddMembers =>
(isAdmin || _conversation.canEveryoneAddMembers!) &&
2021-03-13 10:02:44 +00:00
(!isUpdating || !_conversation.isManaged);
2020-04-26 13:25:56 +00:00
2021-03-13 09:32:11 +00:00
get _isValid => _members.length > 0;
2020-04-26 13:25:56 +00:00
2021-03-13 09:32:11 +00:00
Future<void> _init() async {
2021-03-13 09:48:59 +00:00
if (!isUpdating) {
_admins.add(userID());
return;
}
2021-03-13 09:32:11 +00:00
_conversation =
await ConversationsHelper().getSingle(widget.convID, force: true);
_nameController.text = _conversation.name ?? "";
2021-03-13 10:09:54 +00:00
_colorController.text = _conversation.color == null
? ""
: "#${colorToHex(_conversation.color)}";
2021-03-13 09:32:11 +00:00
_members = await UsersHelper().getList(_conversation.membersID);
_admins = _conversation.adminsID;
_followConversation = _conversation.following;
_canEveryoneAddMembers = _conversation.canEveryoneAddMembers;
2021-03-13 10:33:25 +00:00
_image = _conversation.logoURL;
2021-03-13 10:08:08 +00:00
setState(() {});
2019-05-01 07:24:50 +00:00
}
2019-04-27 14:23:08 +00:00
@override
2021-03-13 09:32:11 +00:00
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
leading: ComunicBackButton(),
title: Text(isUpdating
? tr("Update conversation")!
: tr("Create a conversation")!),
2021-03-13 09:32:11 +00:00
actions: [
IconButton(
icon: Icon(Icons.check),
onPressed: _isValid ? _submitForm : null)
],
),
body: AsyncScreenWidget(
onReload: _init,
onBuild: _buildBody,
errorMessage: tr("Failed to load conversation settings!")!,
2021-03-13 09:32:11 +00:00
),
);
Widget _buildBody() {
2020-04-26 13:25:56 +00:00
return SingleChildScrollView(
child: Container(
padding: EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
2021-04-06 16:41:51 +00:00
_isGroupConversation
? Text(tr("This conversation is managed by a group")!)
2021-04-06 16:41:51 +00:00
: Container(),
2020-04-26 13:25:56 +00:00
// Conversation name
TextField(
controller: _nameController,
decoration: InputDecoration(
2021-03-13 09:09:17 +00:00
labelText: tr("Conversation name (optional)"),
2020-04-26 13:25:56 +00:00
alignLabelWithHint: true,
2021-03-10 16:54:41 +00:00
enabled: isAdmin,
),
2020-04-26 13:25:56 +00:00
),
2021-03-13 09:09:17 +00:00
// Conversation color
TextField(
controller: _colorController,
onChanged: (s) => setState(() {}),
decoration: InputDecoration(
labelText: tr("Conversation color (optional)"),
alignLabelWithHint: true,
enabled: isAdmin,
suffixIcon: IconButton(
icon: Icon(Icons.colorize),
color: _color,
onPressed: isAdmin ? _pickColor : null,
)),
),
2020-04-26 13:25:56 +00:00
// Follow conversation ?
Row(
children: <Widget>[
Switch.adaptive(
value: _followConversation,
onChanged: (b) => setState(() {
_followConversation = b;
}),
),
Text(tr("Follow conversation")!)
2020-04-26 13:25:56 +00:00
],
),
// Allow every members of the conversation to add users ?
2021-04-06 16:41:51 +00:00
_isGroupConversation
? Container()
: Row(
children: <Widget>[
Switch.adaptive(
value: _canEveryoneAddMembers!,
2021-04-06 16:41:51 +00:00
onChanged: isAdmin
? (b) => setState(() {
_canEveryoneAddMembers = b;
})
: null,
),
Flexible(
child: Text(tr(
"Allow all members of the conversation to add users")!))
2021-04-06 16:41:51 +00:00
],
),
2020-04-26 13:25:56 +00:00
// Add a member to the conversation
PickUserWidget(
2021-03-13 09:52:53 +00:00
resetOnChoose: true,
keepFocusOnChoose: true,
label: tr("Add member")!,
2021-03-13 09:52:53 +00:00
enabled: _canAddMembers,
onSelectUser: (user) => _addMember(user)),
2020-04-26 13:25:56 +00:00
//Conversation members
Column(
2021-03-13 09:48:59 +00:00
children: _members.map((f) => _buildMemberTile(f)).toList(),
2020-04-26 13:25:56 +00:00
),
2021-03-13 10:42:58 +00:00
// Conversation image
isUpdating ? _buildConversationImageWidget() : Container(),
2020-04-26 13:25:56 +00:00
],
),
2019-04-27 14:23:08 +00:00
),
);
}
2021-03-13 09:48:59 +00:00
Widget _buildMemberTile(User user) => SimpleUserTile(
user: user,
subtitle: _admins.contains(user.id) ? tr("Admin") : tr("Member"),
trailing: _canAddMembers
? PopupMenuButton<_MembersMenuChoices>(
onSelected: (choice) => _membersMenuItemSelected(user, choice),
itemBuilder: (c) => <PopupMenuEntry<_MembersMenuChoices>>[
PopupMenuItem(
child: Text(tr("Toggle admin status")!),
2021-03-13 09:48:59 +00:00
value: _MembersMenuChoices.TOGGLE_ADMIN_STATUS,
enabled: isUpdating && isAdmin && user.id != userID(),
),
PopupMenuItem(
child: Text(tr("Remove")!),
2021-03-13 09:48:59 +00:00
value: _MembersMenuChoices.REMOVE,
enabled: isAdmin && user.id != userID(),
),
],
)
: null,
);
2021-03-13 09:09:17 +00:00
void _pickColor() async {
final color = await showColorPickerDialog(context, _color);
setState(() =>
_colorController.text = color == null ? "" : "#${colorToHex(color)}");
}
2019-04-27 14:23:08 +00:00
/// An option of the members menu has been selected
2020-04-26 13:25:56 +00:00
void _membersMenuItemSelected(User user, _MembersMenuChoices choice) {
2021-03-13 09:48:59 +00:00
switch (choice) {
case _MembersMenuChoices.REMOVE:
_removeMember(user);
break;
case _MembersMenuChoices.TOGGLE_ADMIN_STATUS:
2021-03-13 10:02:44 +00:00
_toggleAdminStatus(user);
2021-03-13 09:48:59 +00:00
break;
}
}
2021-03-13 09:52:53 +00:00
void _addMember(User user) async {
try {
if (_members.contains(user)) return;
if (isUpdating)
await ConversationsHelper.addMember(_conversation.id, user.id);
setState(() => _members.insert(0, user));
} catch (e, s) {
logError(e, s);
snack(context, tr("Failed to add member to conversation!")!);
2021-03-13 09:52:53 +00:00
}
}
2021-03-13 09:48:59 +00:00
void _removeMember(User user) async {
try {
if (isUpdating)
await ConversationsHelper.removeMember(_conversation.id, user.id);
2020-04-26 13:25:56 +00:00
setState(() {
_members.removeWhere((u) => u.id == user.id);
2021-03-13 09:48:59 +00:00
_admins.remove(user.id);
2019-04-27 14:23:08 +00:00
});
2021-03-13 09:48:59 +00:00
} catch (e, s) {
logError(e, s);
snack(context, tr("Failed to remove member!")!);
2021-03-13 09:48:59 +00:00
}
2019-04-27 14:23:08 +00:00
}
2021-03-13 10:02:44 +00:00
void _toggleAdminStatus(User user) async {
try {
final setAdmin = !_admins.contains(user.id);
2022-03-11 15:21:35 +00:00
await ConversationsHelper.setAdmin(_conversation.id!, user.id, setAdmin);
2021-03-13 10:02:44 +00:00
setState(() {
if (!setAdmin)
_admins.remove(user.id);
else
_admins.add(user.id);
});
} catch (e, s) {
logError(e, s);
snack(context, tr("Failed to toggle admin status of user!")!);
2021-03-13 10:02:44 +00:00
}
}
2019-04-27 14:23:08 +00:00
/// Submit the conversation
Future<void> _submitForm() async {
2021-03-13 09:32:11 +00:00
try {
// Create the conversation
if (!isUpdating) {
final conversationID = await ConversationsHelper.createConversation(
NewConversation(
name: _nameController.text,
members: _members.map((element) => element.id).toList(),
follow: _followConversation,
canEveryoneAddMembers: _canEveryoneAddMembers!,
2022-03-11 15:40:56 +00:00
color: _color));
2021-03-13 09:32:11 +00:00
MainController.of(context)!.popPage();
MainController.of(context)!.openConversationById(conversationID);
2021-03-13 10:08:08 +00:00
return;
2021-03-13 09:32:11 +00:00
}
2021-03-13 10:08:08 +00:00
// Update conversation settings
final newSettings = NewConversationsSettings(
convID: _conversation.id!,
2019-05-01 07:24:50 +00:00
following: _followConversation,
2021-03-13 10:08:08 +00:00
isComplete: isAdmin,
name: _nameController.text,
2020-04-26 12:02:57 +00:00
canEveryoneAddMembers: _canEveryoneAddMembers,
2021-03-13 10:08:08 +00:00
color: _color,
);
2019-05-01 07:24:50 +00:00
2021-03-13 10:08:08 +00:00
await ConversationsHelper.updateConversation(newSettings);
MainController.of(context)!.popPage();
2021-03-13 09:32:11 +00:00
} catch (e, s) {
logError(e, s);
snack(context, tr("Failed to update conversation settings!")!);
2021-03-13 09:32:11 +00:00
}
2019-04-27 14:23:08 +00:00
}
2021-03-13 10:33:25 +00:00
/// Conversation image management
Widget _buildConversationImageWidget() => Column(
children: [
SizedBox(height: 10),
Text(tr("Conversation logo")!,
2021-03-13 10:33:25 +00:00
style: TextStyle(fontWeight: FontWeight.bold)),
SizedBox(height: 5),
_image == null
? Text("No logo defined yet.")
: CachedNetworkImage(imageUrl: _image!),
2021-03-13 10:33:25 +00:00
SizedBox(height: 5),
isAdmin
2021-03-13 10:42:58 +00:00
? Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
2021-03-13 14:28:34 +00:00
OutlinedButton(
2021-03-13 10:42:58 +00:00
onPressed: _uploadNewLogo,
child: Text(tr("Change logo")!),
2021-03-13 10:42:58 +00:00
),
SizedBox(width: 5),
_image == null
? Container()
2021-03-13 14:28:34 +00:00
: ElevatedButton(
2021-03-13 10:42:58 +00:00
onPressed: _deleteLogo,
child: Text(tr("Delete logo")!),
2021-03-13 14:28:34 +00:00
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(Colors.red)),
2021-03-13 10:42:58 +00:00
),
],
2021-03-13 10:33:25 +00:00
)
: Container(),
SizedBox(height: 10),
],
);
/// Upload new conversation logo
Future<void> _uploadNewLogo() async {
try {
final newLogo = await showPickFileDialog(
context: context,
allowedMimeTypes: ["image/png", "image/jpeg", "image/gif"],
imageMaxWidth: srvConfig!.conversationsPolicy.maxLogoWidth,
imageMaxHeight: srvConfig!.conversationsPolicy.maxLogoHeight,
2021-03-13 10:33:25 +00:00
);
if (newLogo == null) return;
await ConversationsHelper.changeImage(_conversation.id, newLogo);
final newConvSettings =
await ConversationsHelper().getSingle(_conversation.id, force: true);
setState(() => _image = newConvSettings.logoURL);
} catch (e, s) {
logError(e, s);
snack(context, tr("Failed to change conversation logo !")!);
2021-03-13 10:33:25 +00:00
}
}
2021-03-13 10:42:58 +00:00
/// Delete conversation logo
Future<void> _deleteLogo() async {
try {
if (!await showConfirmDialog(
context: context,
message: tr("Do you really want to delete this logo?"))) return;
await ConversationsHelper.removeLogo(_conversation.id);
setState(() => _image = null);
} catch (e, s) {
logError(e, s);
snack(context, tr("Failed to remove conversation logo!")!);
2021-03-13 10:42:58 +00:00
}
}
2019-04-27 14:23:08 +00:00
}