mirror of
				https://gitlab.com/comunic/comunicmobile
				synced 2025-11-03 19:54:12 +00:00 
			
		
		
		
	Create new conversations
This commit is contained in:
		@@ -8,6 +8,7 @@ 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';
 | 
			
		||||
@@ -24,6 +25,27 @@ class ConversationsHelper {
 | 
			
		||||
  final ConversationMessagesDatabaseHelper _conversationMessagesDatabaseHelper =
 | 
			
		||||
      ConversationMessagesDatabaseHelper();
 | 
			
		||||
 | 
			
		||||
  /// Create a new conversation
 | 
			
		||||
  ///
 | 
			
		||||
  /// Return the ID of the newly created conversation or -1 in case of failure
 | 
			
		||||
  Future<int> createConversation(ConversationSettings settings) async {
 | 
			
		||||
 | 
			
		||||
    final response = await APIRequest(
 | 
			
		||||
      uri: "conversations/create",
 | 
			
		||||
      needLogin: true,
 | 
			
		||||
      args: {
 | 
			
		||||
        "name" : settings.hasName ? settings.name : "false",
 | 
			
		||||
        "follow" : settings.following ? "true" : "false",
 | 
			
		||||
        "users": settings.members.join(",")
 | 
			
		||||
      }
 | 
			
		||||
    ).exec();
 | 
			
		||||
 | 
			
		||||
    if(response.code != 200) return -1;
 | 
			
		||||
 | 
			
		||||
    return response.getObject()["conversationID"];
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Download the list of conversations from the server
 | 
			
		||||
  Future<ConversationsList> downloadList() async {
 | 
			
		||||
    final response =
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								lib/models/conversation_settings.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								lib/models/conversation_settings.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
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<int> 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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								lib/ui/routes/create_conversation_route.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								lib/ui/routes/create_conversation_route.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
import 'package:comunic/ui/screens/update_conversation_screen.dart';
 | 
			
		||||
import 'package:comunic/utils/intl_utils.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
/// Create a new conversation route
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre HUBERT
 | 
			
		||||
 | 
			
		||||
class CreateConversationRoute extends StatelessWidget {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        title: Text(tr("Create a conversation")),
 | 
			
		||||
      ),
 | 
			
		||||
 | 
			
		||||
      body: UpdateConversationScreen(),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -3,6 +3,7 @@ import 'package:comunic/helpers/conversations_helper.dart';
 | 
			
		||||
import 'package:comunic/helpers/users_helper.dart';
 | 
			
		||||
import 'package:comunic/lists/conversations_list.dart';
 | 
			
		||||
import 'package:comunic/ui/routes/conversation_route.dart';
 | 
			
		||||
import 'package:comunic/ui/routes/create_conversation_route.dart';
 | 
			
		||||
import 'package:comunic/ui/tiles/conversation_tile.dart';
 | 
			
		||||
import 'package:comunic/utils/intl_utils.dart';
 | 
			
		||||
import 'package:comunic/utils/ui_utils.dart';
 | 
			
		||||
@@ -52,7 +53,7 @@ class _ConversationScreenState extends State<ConversationsListScreen> {
 | 
			
		||||
 | 
			
		||||
    //Get the list of conversations
 | 
			
		||||
    var list;
 | 
			
		||||
    if(cached)
 | 
			
		||||
    if (cached)
 | 
			
		||||
      list = await _conversationsHelper.getCachedList();
 | 
			
		||||
    else
 | 
			
		||||
      list = await _conversationsHelper.downloadList();
 | 
			
		||||
@@ -90,12 +91,20 @@ class _ConversationScreenState extends State<ConversationsListScreen> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Open a conversation
 | 
			
		||||
  void _openConversation(BuildContext context, int conversationId){
 | 
			
		||||
    Navigator.of(context).push(MaterialPageRoute(builder: (c){
 | 
			
		||||
      return ConversationRoute(conversationID: conversationId,);
 | 
			
		||||
  void _openConversation(BuildContext context, int conversationId) {
 | 
			
		||||
    Navigator.of(context).push(MaterialPageRoute(builder: (c) {
 | 
			
		||||
      return ConversationRoute(
 | 
			
		||||
        conversationID: conversationId,
 | 
			
		||||
      );
 | 
			
		||||
    }));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Create a new conversation
 | 
			
		||||
  void _createConversation(BuildContext context) {
 | 
			
		||||
    Navigator.of(context)
 | 
			
		||||
        .push(MaterialPageRoute(builder: (c) => CreateConversationRoute()));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    if (_error == LoadErrorLevel.MAJOR) return _buildErrorCard();
 | 
			
		||||
@@ -116,7 +125,9 @@ class _ConversationScreenState extends State<ConversationsListScreen> {
 | 
			
		||||
                  return ConversationTile(
 | 
			
		||||
                    conversation: _list.elementAt(index),
 | 
			
		||||
                    usersList: _list.users,
 | 
			
		||||
                    onOpen: (c){_openConversation(context, c.id);},
 | 
			
		||||
                    onOpen: (c) {
 | 
			
		||||
                      _openConversation(context, c.id);
 | 
			
		||||
                    },
 | 
			
		||||
                  );
 | 
			
		||||
                },
 | 
			
		||||
                itemCount: _list.length,
 | 
			
		||||
@@ -124,6 +135,18 @@ class _ConversationScreenState extends State<ConversationsListScreen> {
 | 
			
		||||
            ),
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
 | 
			
		||||
        // Add conversation button
 | 
			
		||||
        Positioned(
 | 
			
		||||
          right: 20.0,
 | 
			
		||||
          bottom: 20.0,
 | 
			
		||||
          child: FloatingActionButton(
 | 
			
		||||
            onPressed: () => _createConversation(context),
 | 
			
		||||
            child: Icon(Icons.add),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
 | 
			
		||||
        // Loading indicator
 | 
			
		||||
        Positioned(
 | 
			
		||||
          top: 8.0,
 | 
			
		||||
          left: 0.0,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										132
									
								
								lib/ui/screens/update_conversation_screen.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								lib/ui/screens/update_conversation_screen.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,132 @@
 | 
			
		||||
import 'package:comunic/helpers/conversations_helper.dart';
 | 
			
		||||
import 'package:comunic/lists/users_list.dart';
 | 
			
		||||
import 'package:comunic/models/conversation_settings.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';
 | 
			
		||||
import 'package:comunic/utils/intl_utils.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
/// Create / Update conversation screen
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre HUBERT
 | 
			
		||||
 | 
			
		||||
enum _MembersMenuChoices { REMOVE }
 | 
			
		||||
 | 
			
		||||
class UpdateConversationScreen extends StatefulWidget {
 | 
			
		||||
  @override
 | 
			
		||||
  State<StatefulWidget> createState() => _UpdateConversationScreen();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _UpdateConversationScreen extends State<UpdateConversationScreen> {
 | 
			
		||||
  TextEditingController _nameController = TextEditingController();
 | 
			
		||||
  UsersList _members = UsersList();
 | 
			
		||||
  bool _followConversation = true;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Container(
 | 
			
		||||
      padding: EdgeInsets.all(8.0),
 | 
			
		||||
      child: Column(
 | 
			
		||||
        children: <Widget>[
 | 
			
		||||
          // Conversation name
 | 
			
		||||
          TextField(
 | 
			
		||||
            controller: _nameController,
 | 
			
		||||
            decoration: InputDecoration(
 | 
			
		||||
                labelText: tr("Conversation name (optionnal)"),
 | 
			
		||||
                alignLabelWithHint: true),
 | 
			
		||||
          ),
 | 
			
		||||
 | 
			
		||||
          // Add a member to the conversation
 | 
			
		||||
          PickUserWidget(
 | 
			
		||||
            resetOnChoose: true,
 | 
			
		||||
            keepFocusOnChoose: true,
 | 
			
		||||
            label: tr("Add member"),
 | 
			
		||||
            onSelectUser: (user) => setState(() {
 | 
			
		||||
                  if (!_members.contains(user)) _members.add(user);
 | 
			
		||||
                }),
 | 
			
		||||
          ),
 | 
			
		||||
 | 
			
		||||
          //Conversation members
 | 
			
		||||
          Container(
 | 
			
		||||
            child: _members.length == 0
 | 
			
		||||
                ? null
 | 
			
		||||
                : Flexible(
 | 
			
		||||
                    child: ListView.builder(
 | 
			
		||||
                      itemCount: _members.length,
 | 
			
		||||
                      itemBuilder: (b, i) {
 | 
			
		||||
                        return SimpleUserTile(
 | 
			
		||||
                          user: _members[i],
 | 
			
		||||
                          trailing: PopupMenuButton<_MembersMenuChoices>(
 | 
			
		||||
                            onSelected: (choice) =>
 | 
			
		||||
                                _membersMenuItemSelected(i, choice),
 | 
			
		||||
                            itemBuilder: (c) =>
 | 
			
		||||
                                <PopupMenuEntry<_MembersMenuChoices>>[
 | 
			
		||||
                                  PopupMenuItem(
 | 
			
		||||
                                    child: Text(tr("Remove")),
 | 
			
		||||
                                    value: _MembersMenuChoices.REMOVE,
 | 
			
		||||
                                  )
 | 
			
		||||
                                ],
 | 
			
		||||
                          ),
 | 
			
		||||
                        );
 | 
			
		||||
                      },
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
          ),
 | 
			
		||||
 | 
			
		||||
          // Follow conversation ?
 | 
			
		||||
          Row(
 | 
			
		||||
            children: <Widget>[
 | 
			
		||||
              Switch(
 | 
			
		||||
                value: _followConversation,
 | 
			
		||||
                onChanged: (b) => setState(() {
 | 
			
		||||
                      _followConversation = b;
 | 
			
		||||
                    }),
 | 
			
		||||
              ),
 | 
			
		||||
              Text(tr("Follow conversation"))
 | 
			
		||||
            ],
 | 
			
		||||
          ),
 | 
			
		||||
 | 
			
		||||
          // Submit button
 | 
			
		||||
          RaisedButton(
 | 
			
		||||
            onPressed: _members.length < 1 ? null : _submitForm,
 | 
			
		||||
            child: Text(tr("Create the conversation")),
 | 
			
		||||
          )
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// An option of the members menu has been selected
 | 
			
		||||
  void _membersMenuItemSelected(int index, _MembersMenuChoices choice) {
 | 
			
		||||
    if (choice == null) return;
 | 
			
		||||
 | 
			
		||||
    if (choice == _MembersMenuChoices.REMOVE)
 | 
			
		||||
      return setState(() {
 | 
			
		||||
        _members.removeAt(index);
 | 
			
		||||
      });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Submit the conversation
 | 
			
		||||
  Future<void> _submitForm() async {
 | 
			
		||||
    final settings = ConversationSettings(
 | 
			
		||||
      id: 0,
 | 
			
		||||
      name: _nameController.text,
 | 
			
		||||
      following: _followConversation,
 | 
			
		||||
      members: _members.usersID,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Create the conversation
 | 
			
		||||
    final conversationID = await ConversationsHelper().createConversation(settings);
 | 
			
		||||
 | 
			
		||||
    // Check for errors
 | 
			
		||||
    if(conversationID < 1)
 | 
			
		||||
      return Scaffold.of(context).showSnackBar(
 | 
			
		||||
          SnackBar(content: Text(tr("Could not create the conversation!")), duration: Duration(seconds: 1),));
 | 
			
		||||
 | 
			
		||||
    // Open the conversation
 | 
			
		||||
    Navigator.of(context).pushReplacement(MaterialPageRoute(
 | 
			
		||||
      builder: (c) => ConversationRoute(conversationID: conversationID,)
 | 
			
		||||
    ));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -13,8 +13,9 @@ typedef OnUserTap = void Function(User);
 | 
			
		||||
class SimpleUserTile extends StatelessWidget {
 | 
			
		||||
  final User user;
 | 
			
		||||
  final OnUserTap onTap;
 | 
			
		||||
  final Widget trailing;
 | 
			
		||||
 | 
			
		||||
  const SimpleUserTile({Key key, this.user, this.onTap})
 | 
			
		||||
  const SimpleUserTile({Key key, this.user, this.onTap, this.trailing})
 | 
			
		||||
      : assert(user != null),
 | 
			
		||||
        super(key: key);
 | 
			
		||||
 | 
			
		||||
@@ -26,6 +27,7 @@ class SimpleUserTile extends StatelessWidget {
 | 
			
		||||
        user: user,
 | 
			
		||||
      ),
 | 
			
		||||
      title: Text(user.fullName),
 | 
			
		||||
      trailing: trailing,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,6 @@ import 'package:comunic/helpers/search_helper.dart';
 | 
			
		||||
import 'package:comunic/lists/users_list.dart';
 | 
			
		||||
import 'package:comunic/models/user.dart';
 | 
			
		||||
import 'package:comunic/ui/tiles/simple_user_tile.dart';
 | 
			
		||||
import 'package:comunic/utils/intl_utils.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
/// Pick user widget
 | 
			
		||||
@@ -16,9 +15,20 @@ typedef OnSelectUserCallback = void Function(User);
 | 
			
		||||
 | 
			
		||||
class PickUserWidget extends StatefulWidget {
 | 
			
		||||
  final OnSelectUserCallback onSelectUser;
 | 
			
		||||
  final String label;
 | 
			
		||||
  final bool resetOnChoose;
 | 
			
		||||
  final bool keepFocusOnChoose;
 | 
			
		||||
 | 
			
		||||
  const PickUserWidget({Key key, @required this.onSelectUser})
 | 
			
		||||
  const PickUserWidget(
 | 
			
		||||
      {Key key,
 | 
			
		||||
      @required this.onSelectUser,
 | 
			
		||||
      @required this.label,
 | 
			
		||||
      this.resetOnChoose = false,
 | 
			
		||||
      this.keepFocusOnChoose = false})
 | 
			
		||||
      : assert(onSelectUser != null),
 | 
			
		||||
        assert(label != null),
 | 
			
		||||
        assert(resetOnChoose != null),
 | 
			
		||||
        assert(keepFocusOnChoose != null),
 | 
			
		||||
        super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@@ -42,8 +52,7 @@ class _PickUserWidgetState extends State<PickUserWidget> {
 | 
			
		||||
    _focusNode.addListener(() {
 | 
			
		||||
      if (_focusNode.hasFocus) {
 | 
			
		||||
        //Check for focus
 | 
			
		||||
        _overlayEntry = _createOverlayEntry();
 | 
			
		||||
        Overlay.of(context).insert(_overlayEntry);
 | 
			
		||||
        //_showOverlay();
 | 
			
		||||
      } else {
 | 
			
		||||
        //Remove overlay
 | 
			
		||||
        _removeOverlay();
 | 
			
		||||
@@ -57,7 +66,10 @@ class _PickUserWidgetState extends State<PickUserWidget> {
 | 
			
		||||
      focusNode: _focusNode,
 | 
			
		||||
      onChanged: (s) => _updateSuggestions(),
 | 
			
		||||
      controller: _controller,
 | 
			
		||||
      decoration: InputDecoration(labelText: tr("Select user")),
 | 
			
		||||
      decoration: InputDecoration(
 | 
			
		||||
        labelText: widget.label,
 | 
			
		||||
        alignLabelWithHint: true,
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -87,6 +99,11 @@ class _PickUserWidgetState extends State<PickUserWidget> {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _showOverlay() {
 | 
			
		||||
    _overlayEntry = _createOverlayEntry();
 | 
			
		||||
    Overlay.of(context).insert(_overlayEntry);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _removeOverlay() {
 | 
			
		||||
    if (_overlayEntry != null) {
 | 
			
		||||
      _overlayEntry.remove();
 | 
			
		||||
@@ -96,22 +113,36 @@ class _PickUserWidgetState extends State<PickUserWidget> {
 | 
			
		||||
 | 
			
		||||
  /// This method get called each time the input value is updated
 | 
			
		||||
  Future<void> _updateSuggestions() async {
 | 
			
		||||
    if (_controller.value.text.length == 0) return;
 | 
			
		||||
    if (_controller.value.text.length == 0) return _removeOverlay();
 | 
			
		||||
 | 
			
		||||
    final results = await _searchHelper.searchUser(_controller.value.text);
 | 
			
		||||
 | 
			
		||||
    if (results == null) return;
 | 
			
		||||
 | 
			
		||||
    _suggestions = results;
 | 
			
		||||
    if (_overlayEntry != null) _overlayEntry.markNeedsBuild();
 | 
			
		||||
    if (_overlayEntry != null)
 | 
			
		||||
      _overlayEntry.markNeedsBuild();
 | 
			
		||||
    else
 | 
			
		||||
      _showOverlay();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Method called each time a user is tapped (selected)
 | 
			
		||||
  void _userTapped(User user) {
 | 
			
		||||
    _controller.text = user.fullName;
 | 
			
		||||
    // Hide overlay
 | 
			
		||||
    _removeOverlay();
 | 
			
		||||
    _focusNode.unfocus();
 | 
			
		||||
 | 
			
		||||
    // Unfocus if required
 | 
			
		||||
    if (!widget.keepFocusOnChoose) {
 | 
			
		||||
      _focusNode.unfocus();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //Check if name has to remain in input
 | 
			
		||||
    if (widget.resetOnChoose) {
 | 
			
		||||
      _controller.text = "";
 | 
			
		||||
    } else
 | 
			
		||||
      _controller.text = user.fullName;
 | 
			
		||||
 | 
			
		||||
    //Callback
 | 
			
		||||
    widget.onSelectUser(user);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user