From e7c34e6e71fa9c51a364909d0f45ece709bc448e Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Wed, 1 May 2019 09:24:50 +0200 Subject: [PATCH] Can update a conversation --- lib/helpers/conversations_helper.dart | 24 ++++- lib/models/conversation.dart | 5 + lib/models/conversation_settings.dart | 27 ----- lib/ui/routes/conversation_route.dart | 15 +++ lib/ui/routes/update_conversation_route.dart | 91 ++++++++++++++++ .../screens/update_conversation_screen.dart | 102 +++++++++++++----- lib/ui/widgets/pick_user_widget.dart | 5 +- 7 files changed, 212 insertions(+), 57 deletions(-) delete mode 100644 lib/models/conversation_settings.dart create mode 100644 lib/ui/routes/update_conversation_route.dart diff --git a/lib/helpers/conversations_helper.dart b/lib/helpers/conversations_helper.dart index 3cd4e2c..c7d6246 100644 --- a/lib/helpers/conversations_helper.dart +++ b/lib/helpers/conversations_helper.dart @@ -8,7 +8,6 @@ import 'package:comunic/models/api_request.dart'; import 'package:comunic/models/api_response.dart'; import 'package:comunic/models/conversation.dart'; import 'package:comunic/models/conversation_message.dart'; -import 'package:comunic/models/conversation_settings.dart'; import 'package:comunic/models/new_conversation_message.dart'; import 'package:comunic/utils/account_utils.dart'; import 'package:meta/meta.dart'; @@ -28,7 +27,7 @@ class ConversationsHelper { /// Create a new conversation /// /// Return the ID of the newly created conversation or -1 in case of failure - Future createConversation(ConversationSettings settings) async { + Future createConversation(Conversation settings) async { final response = await APIRequest(uri: "conversations/create", needLogin: true, args: { "name": settings.hasName ? settings.name : "false", @@ -41,6 +40,27 @@ class ConversationsHelper { return response.getObject()["conversationID"]; } + /// Update an existing conversation + /// + /// Returns a boolean depending of the success of the operation + Future updateConversation(Conversation settings) async { + final request = + APIRequest(uri: "conversations/updateSettings", needLogin: true, args: { + "conversationID": settings.id.toString(), + "following": settings.following ? "true" : "false" + }); + + // Update all conversation settings, if possible + if(settings.isOwner) { + request.addString("name", settings.hasName ? settings.name : "false"); + request.addString("members", settings.members.join(",")); + } + + final response = await request.exec(); + + return response.code == 200; + } + /// Download the list of conversations from the server Future downloadList() async { final response = diff --git a/lib/models/conversation.dart b/lib/models/conversation.dart index eb170d8..938d1f9 100644 --- a/lib/models/conversation.dart +++ b/lib/models/conversation.dart @@ -1,5 +1,6 @@ import 'package:comunic/helpers/database/database_contract.dart'; import 'package:comunic/models/cache_model.dart'; +import 'package:comunic/utils/account_utils.dart'; import 'package:comunic/utils/list_utils.dart'; import 'package:meta/meta.dart'; @@ -34,6 +35,10 @@ class Conversation extends CacheModel implements Comparable { /// Check out whether a conversation has a fixed name or not bool get hasName => this.name != null; + /// Check out whether current user of the application is the owner of it or + /// not + bool get isOwner => this.ownerID == userID(); + Conversation.fromMap(Map map) : ownerID = map[ConversationTableContract.C_OWNER_ID], lastActive = map[ConversationTableContract.C_LAST_ACTIVE], diff --git a/lib/models/conversation_settings.dart b/lib/models/conversation_settings.dart deleted file mode 100644 index 932d2bf..0000000 --- a/lib/models/conversation_settings.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:meta/meta.dart'; - -/// Conversation settings model -/// -/// Use this model to create / update a conversation -/// -/// @author Pierre HUBERT - -class ConversationSettings { - /// Set the ID to 0 if not required - final int id; - final String name; - final bool following; - final List members; - - ConversationSettings({ - @required this.id, - @required this.name, - @required this.following, - @required this.members, - }) : assert(members != null && members.length > 0), - assert(following != null); - - bool get hasName => name != null && name.length > 0; - - bool get hasId => id != null && id > 0; -} diff --git a/lib/ui/routes/conversation_route.dart b/lib/ui/routes/conversation_route.dart index 4d217fc..cc084d4 100644 --- a/lib/ui/routes/conversation_route.dart +++ b/lib/ui/routes/conversation_route.dart @@ -1,5 +1,6 @@ import 'package:comunic/helpers/conversations_helper.dart'; import 'package:comunic/models/conversation.dart'; +import 'package:comunic/ui/routes/update_conversation_route.dart'; import 'package:comunic/ui/screens/conversation_screen.dart'; import 'package:comunic/utils/intl_utils.dart'; import 'package:comunic/utils/ui_utils.dart'; @@ -53,6 +54,14 @@ class _ConversationRouteState extends State { }); } + void _openSettings() { + Navigator.of(context).push(MaterialPageRoute(builder: (b) { + return UpdateConversationRoute( + conversationID: widget.conversationID, + ); + })); + } + Widget _buildRouteBody() { //Handle errors if (_error != null && _error) @@ -86,6 +95,12 @@ class _ConversationRouteState extends State { title: Text( _conversationName == null ? tr("Loading") : _conversationName, ), + actions: [ + IconButton( + icon: Icon(Icons.settings), + onPressed: _openSettings, + ) + ], ), body: _buildRouteBody(), ); diff --git a/lib/ui/routes/update_conversation_route.dart b/lib/ui/routes/update_conversation_route.dart new file mode 100644 index 0000000..d8f5075 --- /dev/null +++ b/lib/ui/routes/update_conversation_route.dart @@ -0,0 +1,91 @@ +import 'package:comunic/helpers/conversations_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/ui/screens/update_conversation_screen.dart'; +import 'package:comunic/utils/intl_utils.dart'; +import 'package:comunic/utils/ui_utils.dart'; +import 'package:flutter/material.dart'; + +/// Update a conversation route +/// +/// @author Pierre HUBERT + +class UpdateConversationRoute extends StatefulWidget { + final int conversationID; + + const UpdateConversationRoute({Key key, this.conversationID}) + : assert(conversationID != null), + super(key: key); + + @override + State createState() => _UpdateConversationRoute(); +} + +class _UpdateConversationRoute extends State { + Conversation _conversation; + UsersList _membersInfo; + bool _error = false; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _loadConversation(); + } + + void setError(bool e) => setState(() { + _error = e; + }); + + /// Load information about the being updated conversation + Future _loadConversation() async { + setError(false); + + final conversation = await ConversationsHelper() + .getSingle(widget.conversationID, force: true); + + if (conversation == null) return setError(true); + + //Load information about the members of the conversation + _membersInfo = await UsersHelper().getUsersInfo(conversation.members); + + if (_membersInfo == null) return setError(true); + + setState(() { + _conversation = conversation; + }); + } + + /// Build the body of this widget + Widget _buildBody() { + if (_error) + return buildErrorCard( + tr("Could not load information about the conversation"), + actions: [ + FlatButton( + onPressed: _loadConversation, + child: Text( + tr("Retry").toUpperCase(), + style: TextStyle(color: Colors.white), + ), + ) + ]); + + if (_conversation == null) return buildLoadingPage(); + + return UpdateConversationScreen( + initialUsers: _membersInfo, + initialSettings: _conversation, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(tr("Update a conversation")), + ), + body: _buildBody(), + ); + } +} diff --git a/lib/ui/screens/update_conversation_screen.dart b/lib/ui/screens/update_conversation_screen.dart index 2509cb0..fdaaafe 100644 --- a/lib/ui/screens/update_conversation_screen.dart +++ b/lib/ui/screens/update_conversation_screen.dart @@ -1,6 +1,6 @@ import 'package:comunic/helpers/conversations_helper.dart'; import 'package:comunic/lists/users_list.dart'; -import 'package:comunic/models/conversation_settings.dart'; +import 'package:comunic/models/conversation.dart'; import 'package:comunic/ui/routes/conversation_route.dart'; import 'package:comunic/ui/tiles/simple_user_tile.dart'; import 'package:comunic/ui/widgets/pick_user_widget.dart'; @@ -14,6 +14,15 @@ import 'package:flutter/material.dart'; enum _MembersMenuChoices { REMOVE } class UpdateConversationScreen extends StatefulWidget { + final Conversation initialSettings; + final UsersList initialUsers; + + const UpdateConversationScreen({ + Key key, + this.initialSettings, + this.initialUsers, + }) : super(key: key); + @override State createState() => _UpdateConversationScreen(); } @@ -23,6 +32,22 @@ class _UpdateConversationScreen extends State { UsersList _members = UsersList(); bool _followConversation = true; + get isUpdating => widget.initialSettings != null; + + get isOwner => !isUpdating || widget.initialSettings.isOwner; + + @override + void initState() { + super.initState(); + + // Check if we are updating an existing conversation + if (widget.initialSettings != null) { + _nameController.text = widget.initialSettings.name; + _members = widget.initialUsers; + _followConversation = widget.initialSettings.following; + } + } + @override Widget build(BuildContext context) { return Container( @@ -33,8 +58,10 @@ class _UpdateConversationScreen extends State { TextField( controller: _nameController, decoration: InputDecoration( - labelText: tr("Conversation name (optionnal)"), - alignLabelWithHint: true), + labelText: tr("Conversation name (optionnal)"), + alignLabelWithHint: true, + enabled: isOwner, + ), ), // Add a member to the conversation @@ -42,6 +69,7 @@ class _UpdateConversationScreen extends State { resetOnChoose: true, keepFocusOnChoose: true, label: tr("Add member"), + enabled: isOwner, onSelectUser: (user) => setState(() { if (!_members.contains(user)) _members.add(user); }), @@ -57,17 +85,19 @@ class _UpdateConversationScreen extends State { itemBuilder: (b, i) { return SimpleUserTile( user: _members[i], - trailing: PopupMenuButton<_MembersMenuChoices>( - onSelected: (choice) => - _membersMenuItemSelected(i, choice), - itemBuilder: (c) => - >[ - PopupMenuItem( - child: Text(tr("Remove")), - value: _MembersMenuChoices.REMOVE, - ) - ], - ), + trailing: isOwner + ? PopupMenuButton<_MembersMenuChoices>( + onSelected: (choice) => + _membersMenuItemSelected(i, choice), + itemBuilder: (c) => + >[ + PopupMenuItem( + child: Text(tr("Remove")), + value: _MembersMenuChoices.REMOVE, + ) + ], + ) + : null, ); }, ), @@ -90,7 +120,9 @@ class _UpdateConversationScreen extends State { // Submit button RaisedButton( onPressed: _members.length < 1 ? null : _submitForm, - child: Text(tr("Create the conversation")), + child: Text(isUpdating + ? tr("Update the conversation") + : tr("Create the conversation")), ) ], ), @@ -109,24 +141,40 @@ class _UpdateConversationScreen extends State { /// Submit the conversation Future _submitForm() async { - final settings = ConversationSettings( - id: 0, - name: _nameController.text, - following: _followConversation, - members: _members.usersID, - ); + final settings = Conversation( + id: isUpdating ? widget.initialSettings.id : 0, + name: _nameController.text, + following: _followConversation, + members: _members.usersID, + + // Give random value to these fields as they are ignored here + lastActive: 0, + ownerID: 0, + sawLastMessage: true); // Create the conversation - final conversationID = await ConversationsHelper().createConversation(settings); + var conversationID = settings.id; + bool error = false; + if (isUpdating) + error = !(await ConversationsHelper().updateConversation(settings)); + else { + conversationID = await ConversationsHelper().createConversation(settings); + if (conversationID < 1) error = true; + } // Check for errors - if(conversationID < 1) - return Scaffold.of(context).showSnackBar( - SnackBar(content: Text(tr("Could not create the conversation!")), duration: Duration(seconds: 1),)); + if (error) + return Scaffold.of(context).showSnackBar(SnackBar( + content: Text(isUpdating + ? tr("Could not update the conversation!") + : tr("Could not create the conversation!")), + duration: Duration(seconds: 1), + )); // Open the conversation Navigator.of(context).pushReplacement(MaterialPageRoute( - builder: (c) => ConversationRoute(conversationID: conversationID,) - )); + builder: (c) => ConversationRoute( + conversationID: conversationID, + ))); } } diff --git a/lib/ui/widgets/pick_user_widget.dart b/lib/ui/widgets/pick_user_widget.dart index 4014e1a..538df04 100644 --- a/lib/ui/widgets/pick_user_widget.dart +++ b/lib/ui/widgets/pick_user_widget.dart @@ -18,13 +18,15 @@ class PickUserWidget extends StatefulWidget { final String label; final bool resetOnChoose; final bool keepFocusOnChoose; + final bool enabled; const PickUserWidget( {Key key, @required this.onSelectUser, @required this.label, this.resetOnChoose = false, - this.keepFocusOnChoose = false}) + this.keepFocusOnChoose = false, + this.enabled = true}) : assert(onSelectUser != null), assert(label != null), assert(resetOnChoose != null), @@ -66,6 +68,7 @@ class _PickUserWidgetState extends State { focusNode: _focusNode, onChanged: (s) => _updateSuggestions(), controller: _controller, + enabled: widget.enabled, decoration: InputDecoration( labelText: widget.label, alignLabelWithHint: true,