mirror of
https://gitlab.com/comunic/comunicmobile
synced 2024-11-25 22:39:22 +00:00
Create new conversations
This commit is contained in:
parent
765ca82300
commit
852064e82d
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user