mirror of
https://gitlab.com/comunic/comunicmobile
synced 2024-11-22 21:09:21 +00:00
409 lines
13 KiB
Dart
409 lines
13 KiB
Dart
import 'package:cached_network_image/cached_network_image.dart';
|
|
import 'package:comunic/helpers/conversations_helper.dart';
|
|
import 'package:comunic/helpers/server_config_helper.dart';
|
|
import 'package:comunic/helpers/users_helper.dart';
|
|
import 'package:comunic/lists/users_list.dart';
|
|
import 'package:comunic/models/conversation.dart';
|
|
import 'package:comunic/models/new_conversation.dart';
|
|
import 'package:comunic/models/new_conversation_settings.dart';
|
|
import 'package:comunic/models/user.dart';
|
|
import 'package:comunic/ui/dialogs/color_picker_dialog.dart';
|
|
import 'package:comunic/ui/dialogs/pick_file_dialog.dart';
|
|
import 'package:comunic/ui/routes/main_route/main_route.dart';
|
|
import 'package:comunic/ui/tiles/simple_user_tile.dart';
|
|
import 'package:comunic/ui/widgets/async_screen_widget.dart';
|
|
import 'package:comunic/ui/widgets/comunic_back_button_widget.dart';
|
|
import 'package:comunic/ui/widgets/pick_user_widget.dart';
|
|
import 'package:comunic/utils/account_utils.dart';
|
|
import 'package:comunic/utils/color_utils.dart';
|
|
import 'package:comunic/utils/dart_color.dart';
|
|
import 'package:comunic/utils/intl_utils.dart';
|
|
import 'package:comunic/utils/log_utils.dart';
|
|
import 'package:comunic/utils/ui_utils.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
/// Create / Update conversation screen
|
|
///
|
|
/// @author Pierre HUBERT
|
|
|
|
enum _MembersMenuChoices { TOGGLE_ADMIN_STATUS, REMOVE }
|
|
|
|
class UpdateConversationScreen extends StatefulWidget {
|
|
final convID;
|
|
|
|
const UpdateConversationScreen({
|
|
Key? key,
|
|
this.convID,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
State<StatefulWidget> createState() => _UpdateConversationScreen();
|
|
}
|
|
|
|
class _UpdateConversationScreen extends State<UpdateConversationScreen> {
|
|
late Conversation _conversation;
|
|
|
|
TextEditingController _nameController = TextEditingController();
|
|
TextEditingController _colorController = TextEditingController();
|
|
UsersList _members = UsersList();
|
|
Set<int?> _admins = Set();
|
|
bool _followConversation = true;
|
|
bool? _canEveryoneAddMembers = true;
|
|
String? _image;
|
|
|
|
String get _conversationColor => _colorController.text;
|
|
|
|
Color? get _color {
|
|
if (_conversationColor.isEmpty) return null;
|
|
|
|
try {
|
|
return HexColor(_conversationColor);
|
|
} catch (e, s) {
|
|
logError(e, s);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
get isUpdating => widget.convID != null;
|
|
|
|
get isAdmin => !isUpdating || _conversation.isAdmin;
|
|
|
|
bool get _isGroupConversation =>
|
|
isUpdating && _conversation.isGroupConversation;
|
|
|
|
bool get _canAddMembers =>
|
|
(isAdmin || _conversation.canEveryoneAddMembers!) &&
|
|
(!isUpdating || !_conversation.isManaged);
|
|
|
|
get _isValid => _members.length > 0;
|
|
|
|
Future<void> _init() async {
|
|
if (!isUpdating) {
|
|
_admins.add(userID());
|
|
return;
|
|
}
|
|
|
|
_conversation =
|
|
await ConversationsHelper().getSingle(widget.convID, force: true);
|
|
|
|
_nameController.text = _conversation.name ?? "";
|
|
_colorController.text = _conversation.color == null
|
|
? ""
|
|
: "#${colorToHex(_conversation.color)}";
|
|
_members = await UsersHelper().getList(_conversation.membersID);
|
|
_admins = _conversation.adminsID;
|
|
_followConversation = _conversation.following;
|
|
_canEveryoneAddMembers = _conversation.canEveryoneAddMembers;
|
|
_image = _conversation.logoURL;
|
|
|
|
setState(() {});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) => Scaffold(
|
|
appBar: AppBar(
|
|
leading: ComunicBackButton(),
|
|
title: Text(isUpdating
|
|
? tr("Update conversation")!
|
|
: tr("Create a conversation")!),
|
|
actions: [
|
|
IconButton(
|
|
icon: Icon(Icons.check),
|
|
onPressed: _isValid ? _submitForm : null)
|
|
],
|
|
),
|
|
body: AsyncScreenWidget(
|
|
onReload: _init,
|
|
onBuild: _buildBody,
|
|
errorMessage: tr("Failed to load conversation settings!")!,
|
|
),
|
|
);
|
|
|
|
Widget _buildBody() {
|
|
return SingleChildScrollView(
|
|
child: Container(
|
|
padding: EdgeInsets.all(8.0),
|
|
child: Column(
|
|
children: <Widget>[
|
|
_isGroupConversation
|
|
? Text(tr("This conversation is managed by a group")!)
|
|
: Container(),
|
|
|
|
// Conversation name
|
|
TextField(
|
|
controller: _nameController,
|
|
decoration: InputDecoration(
|
|
labelText: tr("Conversation name (optional)"),
|
|
alignLabelWithHint: true,
|
|
enabled: isAdmin,
|
|
),
|
|
),
|
|
|
|
// 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,
|
|
)),
|
|
),
|
|
|
|
// Follow conversation ?
|
|
Row(
|
|
children: <Widget>[
|
|
Switch.adaptive(
|
|
value: _followConversation,
|
|
onChanged: (b) => setState(() {
|
|
_followConversation = b;
|
|
}),
|
|
),
|
|
Text(tr("Follow conversation")!)
|
|
],
|
|
),
|
|
|
|
// Allow every members of the conversation to add users ?
|
|
_isGroupConversation
|
|
? Container()
|
|
: Row(
|
|
children: <Widget>[
|
|
Switch.adaptive(
|
|
value: _canEveryoneAddMembers!,
|
|
onChanged: isAdmin
|
|
? (b) => setState(() {
|
|
_canEveryoneAddMembers = b;
|
|
})
|
|
: null,
|
|
),
|
|
Flexible(
|
|
child: Text(tr(
|
|
"Allow all members of the conversation to add users")!))
|
|
],
|
|
),
|
|
|
|
// Add a member to the conversation
|
|
PickUserWidget(
|
|
resetOnChoose: true,
|
|
keepFocusOnChoose: true,
|
|
label: tr("Add member")!,
|
|
enabled: _canAddMembers,
|
|
onSelectUser: (user) => _addMember(user)),
|
|
|
|
//Conversation members
|
|
Column(
|
|
children: _members.map((f) => _buildMemberTile(f)).toList(),
|
|
),
|
|
|
|
// Conversation image
|
|
isUpdating ? _buildConversationImageWidget() : Container(),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
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")!),
|
|
value: _MembersMenuChoices.TOGGLE_ADMIN_STATUS,
|
|
enabled: isUpdating && isAdmin && user.id != userID(),
|
|
),
|
|
PopupMenuItem(
|
|
child: Text(tr("Remove")!),
|
|
value: _MembersMenuChoices.REMOVE,
|
|
enabled: isAdmin && user.id != userID(),
|
|
),
|
|
],
|
|
)
|
|
: null,
|
|
);
|
|
|
|
void _pickColor() async {
|
|
final color = await showColorPickerDialog(context, _color);
|
|
setState(() =>
|
|
_colorController.text = color == null ? "" : "#${colorToHex(color)}");
|
|
}
|
|
|
|
/// An option of the members menu has been selected
|
|
void _membersMenuItemSelected(User user, _MembersMenuChoices choice) {
|
|
switch (choice) {
|
|
case _MembersMenuChoices.REMOVE:
|
|
_removeMember(user);
|
|
break;
|
|
|
|
case _MembersMenuChoices.TOGGLE_ADMIN_STATUS:
|
|
_toggleAdminStatus(user);
|
|
break;
|
|
}
|
|
}
|
|
|
|
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!")!);
|
|
}
|
|
}
|
|
|
|
void _removeMember(User user) async {
|
|
try {
|
|
if (isUpdating)
|
|
await ConversationsHelper.removeMember(_conversation.id, user.id);
|
|
|
|
setState(() {
|
|
_members.removeWhere((u) => u.id == user.id);
|
|
_admins.remove(user.id);
|
|
});
|
|
} catch (e, s) {
|
|
logError(e, s);
|
|
snack(context, tr("Failed to remove member!")!);
|
|
}
|
|
}
|
|
|
|
void _toggleAdminStatus(User user) async {
|
|
try {
|
|
final setAdmin = !_admins.contains(user.id);
|
|
await ConversationsHelper.setAdmin(_conversation.id!, user.id, setAdmin);
|
|
|
|
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!")!);
|
|
}
|
|
}
|
|
|
|
/// Submit the conversation
|
|
Future<void> _submitForm() async {
|
|
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!,
|
|
color: _color));
|
|
|
|
MainController.of(context)!.popPage();
|
|
MainController.of(context)!.openConversationById(conversationID);
|
|
return;
|
|
}
|
|
|
|
// Update conversation settings
|
|
final newSettings = NewConversationsSettings(
|
|
convID: _conversation.id!,
|
|
following: _followConversation,
|
|
isComplete: isAdmin,
|
|
name: _nameController.text,
|
|
canEveryoneAddMembers: _canEveryoneAddMembers,
|
|
color: _color,
|
|
);
|
|
|
|
await ConversationsHelper.updateConversation(newSettings);
|
|
|
|
MainController.of(context)!.popPage();
|
|
} catch (e, s) {
|
|
logError(e, s);
|
|
snack(context, tr("Failed to update conversation settings!")!);
|
|
}
|
|
}
|
|
|
|
/// Conversation image management
|
|
Widget _buildConversationImageWidget() => Column(
|
|
children: [
|
|
SizedBox(height: 10),
|
|
Text(tr("Conversation logo")!,
|
|
style: TextStyle(fontWeight: FontWeight.bold)),
|
|
SizedBox(height: 5),
|
|
_image == null
|
|
? Text("No logo defined yet.")
|
|
: CachedNetworkImage(imageUrl: _image!),
|
|
SizedBox(height: 5),
|
|
isAdmin
|
|
? Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
OutlinedButton(
|
|
onPressed: _uploadNewLogo,
|
|
child: Text(tr("Change logo")!),
|
|
),
|
|
SizedBox(width: 5),
|
|
_image == null
|
|
? Container()
|
|
: ElevatedButton(
|
|
onPressed: _deleteLogo,
|
|
child: Text(tr("Delete logo")!),
|
|
style: ButtonStyle(
|
|
backgroundColor:
|
|
MaterialStateProperty.all(Colors.red)),
|
|
),
|
|
],
|
|
)
|
|
: 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,
|
|
);
|
|
|
|
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 !")!);
|
|
}
|
|
}
|
|
|
|
/// 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!")!);
|
|
}
|
|
}
|
|
}
|