mirror of
https://gitlab.com/comunic/comunicmobile
synced 2024-11-25 22:39:22 +00:00
Can update a conversation message
This commit is contained in:
parent
c8b68e71aa
commit
62125d7c3d
@ -292,6 +292,13 @@ class ConversationsHelper {
|
|||||||
lastMessageID: lastMessageID);
|
lastMessageID: lastMessageID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a single conversation message from the local database
|
||||||
|
///
|
||||||
|
/// Returns the message if found or null in case of failure
|
||||||
|
Future<ConversationMessage> getSingleMessageFromCache(int messageID) async {
|
||||||
|
return await _conversationMessagesDatabaseHelper.get(messageID);
|
||||||
|
}
|
||||||
|
|
||||||
/// Send a new message to the server
|
/// Send a new message to the server
|
||||||
Future<SendMessageResult> sendMessage(NewConversationMessage message) async {
|
Future<SendMessageResult> sendMessage(NewConversationMessage message) async {
|
||||||
final request = APIRequest(
|
final request = APIRequest(
|
||||||
@ -320,27 +327,34 @@ class ConversationsHelper {
|
|||||||
return SendMessageResult.SUCCESS;
|
return SendMessageResult.SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update a message content
|
||||||
|
Future<bool> updateMessage(int id, String newContent) async {
|
||||||
|
final response = await APIRequest(
|
||||||
|
uri: "conversations/updateMessage",
|
||||||
|
needLogin: true,
|
||||||
|
args: {"messageID": id.toString(), "content": newContent}).exec();
|
||||||
|
|
||||||
|
if (response.code != 200) return false;
|
||||||
|
|
||||||
|
// Update the message content locally
|
||||||
|
return await _conversationMessagesDatabaseHelper.updateMessageContent(
|
||||||
|
id: id, newContent: newContent);
|
||||||
|
}
|
||||||
|
|
||||||
/// Delete permanently a message specified by its [id]
|
/// Delete permanently a message specified by its [id]
|
||||||
Future<bool> deleteMessage(int id) async {
|
Future<bool> deleteMessage(int id) async {
|
||||||
|
|
||||||
// Delete the message online
|
// Delete the message online
|
||||||
final response = await APIRequest(
|
final response = await APIRequest(
|
||||||
uri: "conversations/deleteMessage",
|
uri: "conversations/deleteMessage",
|
||||||
needLogin: true,
|
needLogin: true,
|
||||||
args: {
|
args: {"messageID": id.toString()}).exec();
|
||||||
"messageID": id.toString()
|
|
||||||
}
|
|
||||||
).exec();
|
|
||||||
|
|
||||||
if(response.code != 200) return false;
|
|
||||||
|
|
||||||
|
if (response.code != 200) return false;
|
||||||
|
|
||||||
// Delete the message locally
|
// Delete the message locally
|
||||||
return await _conversationMessagesDatabaseHelper.delete(id);
|
return await _conversationMessagesDatabaseHelper.delete(id);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Turn an API response into a ConversationMessage object
|
/// Turn an API response into a ConversationMessage object
|
||||||
ConversationMessage _apiToConversationMessage({
|
ConversationMessage _apiToConversationMessage({
|
||||||
@required int conversationID,
|
@required int conversationID,
|
||||||
|
@ -2,6 +2,7 @@ import 'package:comunic/helpers/database/database_contract.dart';
|
|||||||
import 'package:comunic/helpers/database/model_database_helper.dart';
|
import 'package:comunic/helpers/database/model_database_helper.dart';
|
||||||
import 'package:comunic/lists/conversation_messages_list.dart';
|
import 'package:comunic/lists/conversation_messages_list.dart';
|
||||||
import 'package:comunic/models/conversation_message.dart';
|
import 'package:comunic/models/conversation_message.dart';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
/// Conversation messages database helper
|
/// Conversation messages database helper
|
||||||
///
|
///
|
||||||
@ -34,4 +35,26 @@ class ConversationMessagesDatabaseHelper
|
|||||||
finalList.addAll(list);
|
finalList.addAll(list);
|
||||||
return finalList;
|
return finalList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update the content of a message
|
||||||
|
Future<bool> updateMessageContent({
|
||||||
|
@required int id,
|
||||||
|
@required String newContent,
|
||||||
|
}) async {
|
||||||
|
assert(id != null);
|
||||||
|
assert(newContent != null);
|
||||||
|
|
||||||
|
final message = await get(id);
|
||||||
|
|
||||||
|
if(message == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Update the conversation message using the map
|
||||||
|
final map = message.toMap();
|
||||||
|
map[ConversationsMessagesTableContract.C_MESSAGE] = newContent;
|
||||||
|
|
||||||
|
await insertOrUpdate(ConversationMessage.fromMap(map));
|
||||||
|
|
||||||
|
return true; // Success
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -289,6 +289,7 @@ class _ConversationScreenState extends State<ConversationScreen> {
|
|||||||
userInfo: _usersInfo.getUser(_messages[i].userID),
|
userInfo: _usersInfo.getUser(_messages[i].userID),
|
||||||
isLastMessage: _isLastMessage(i),
|
isLastMessage: _isLastMessage(i),
|
||||||
isFirstMessage: _isFirstMessage(i),
|
isFirstMessage: _isFirstMessage(i),
|
||||||
|
onRequestMessageUpdate: _updateMessage,
|
||||||
onRequestMessageDelete: _deleteMessage,
|
onRequestMessageDelete: _deleteMessage,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@ -389,6 +390,33 @@ class _ConversationScreenState extends State<ConversationScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Request message content update
|
||||||
|
Future<void> _updateMessage(ConversationMessage message) async {
|
||||||
|
final newContent = await askUserString(
|
||||||
|
context: context,
|
||||||
|
title: tr("Update message"),
|
||||||
|
message: tr("Please enter new message content:"),
|
||||||
|
defaultValue: message.message,
|
||||||
|
hint: tr("New message"));
|
||||||
|
|
||||||
|
if (newContent == null) return;
|
||||||
|
|
||||||
|
if (!await _conversationsHelper.updateMessage(message.id, newContent)) {
|
||||||
|
showSimpleSnack(context, tr("Could not update message content!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the new version of the conversation message
|
||||||
|
final newMessage =
|
||||||
|
await _conversationsHelper.getSingleMessageFromCache(message.id);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
final index = _messages.indexOf(message);
|
||||||
|
_messages.insert(index, newMessage);
|
||||||
|
_messages.removeAt(index + 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Request message deletion
|
/// Request message deletion
|
||||||
Future<void> _deleteMessage(ConversationMessage message) async {
|
Future<void> _deleteMessage(ConversationMessage message) async {
|
||||||
final choice = await showDialog<bool>(
|
final choice = await showDialog<bool>(
|
||||||
@ -417,11 +445,10 @@ class _ConversationScreenState extends State<ConversationScreen> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if(choice == null || !choice)
|
if (choice == null || !choice) return;
|
||||||
return;
|
|
||||||
|
|
||||||
// Execute the request
|
// Execute the request
|
||||||
if(!await _conversationsHelper.deleteMessage(message.id))
|
if (!await _conversationsHelper.deleteMessage(message.id))
|
||||||
showSimpleSnack(context, tr("Could not delete conversation message!"));
|
showSimpleSnack(context, tr("Could not delete conversation message!"));
|
||||||
|
|
||||||
// Remove the message from the list
|
// Remove the message from the list
|
||||||
|
@ -12,8 +12,9 @@ import 'package:flutter/material.dart';
|
|||||||
///
|
///
|
||||||
/// @author Pierre HUBERT
|
/// @author Pierre HUBERT
|
||||||
|
|
||||||
enum _MenuChoices { DELETE }
|
enum _MenuChoices { DELETE, REQUEST_UPDATE_CONTENT }
|
||||||
|
|
||||||
|
typedef OnRequestMessageUpdate = void Function(ConversationMessage);
|
||||||
typedef OnRequestMessageDelete = void Function(ConversationMessage);
|
typedef OnRequestMessageDelete = void Function(ConversationMessage);
|
||||||
|
|
||||||
class ConversationMessageTile extends StatelessWidget {
|
class ConversationMessageTile extends StatelessWidget {
|
||||||
@ -21,6 +22,7 @@ class ConversationMessageTile extends StatelessWidget {
|
|||||||
final User userInfo;
|
final User userInfo;
|
||||||
final bool isLastMessage;
|
final bool isLastMessage;
|
||||||
final bool isFirstMessage;
|
final bool isFirstMessage;
|
||||||
|
final OnRequestMessageUpdate onRequestMessageUpdate;
|
||||||
final OnRequestMessageDelete onRequestMessageDelete;
|
final OnRequestMessageDelete onRequestMessageDelete;
|
||||||
|
|
||||||
const ConversationMessageTile({
|
const ConversationMessageTile({
|
||||||
@ -29,11 +31,13 @@ class ConversationMessageTile extends StatelessWidget {
|
|||||||
@required this.userInfo,
|
@required this.userInfo,
|
||||||
@required this.isLastMessage,
|
@required this.isLastMessage,
|
||||||
@required this.isFirstMessage,
|
@required this.isFirstMessage,
|
||||||
|
@required this.onRequestMessageUpdate,
|
||||||
@required this.onRequestMessageDelete,
|
@required this.onRequestMessageDelete,
|
||||||
}) : assert(message != null),
|
}) : assert(message != null),
|
||||||
assert(userInfo != null),
|
assert(userInfo != null),
|
||||||
assert(isLastMessage != null),
|
assert(isLastMessage != null),
|
||||||
assert(isFirstMessage != null),
|
assert(isFirstMessage != null),
|
||||||
|
assert(onRequestMessageUpdate != null),
|
||||||
assert(onRequestMessageDelete != null),
|
assert(onRequestMessageDelete != null),
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
@ -47,6 +51,14 @@ class ConversationMessageTile extends StatelessWidget {
|
|||||||
width: 35.0,
|
width: 35.0,
|
||||||
),
|
),
|
||||||
itemBuilder: (c) => [
|
itemBuilder: (c) => [
|
||||||
|
// Update message content
|
||||||
|
PopupMenuItem(
|
||||||
|
enabled: message.isOwner,
|
||||||
|
value: _MenuChoices.REQUEST_UPDATE_CONTENT,
|
||||||
|
child: Text(tr("Update")),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Delete the message
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
enabled: message.isOwner,
|
enabled: message.isOwner,
|
||||||
value: _MenuChoices.DELETE,
|
value: _MenuChoices.DELETE,
|
||||||
@ -256,12 +268,14 @@ class ConversationMessageTile extends StatelessWidget {
|
|||||||
|
|
||||||
/// Process menu choice
|
/// Process menu choice
|
||||||
void _menuOptionSelected(_MenuChoices value) {
|
void _menuOptionSelected(_MenuChoices value) {
|
||||||
|
switch (value) {
|
||||||
|
case _MenuChoices.REQUEST_UPDATE_CONTENT:
|
||||||
|
onRequestMessageUpdate(message);
|
||||||
|
break;
|
||||||
|
|
||||||
switch(value){
|
|
||||||
case _MenuChoices.DELETE:
|
case _MenuChoices.DELETE:
|
||||||
onRequestMessageDelete(message);
|
onRequestMessageDelete(message);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:comunic/utils/intl_utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
/// User interface utilities
|
/// User interface utilities
|
||||||
|
///
|
||||||
|
/// @author Pierre HUBERT
|
||||||
|
|
||||||
/// Build centered progress bar
|
/// Build centered progress bar
|
||||||
Widget buildCenteredProgressBar() {
|
Widget buildCenteredProgressBar() {
|
||||||
@ -52,17 +55,66 @@ Widget buildErrorCard(String message, {List<Widget> actions}) {
|
|||||||
/// Show an image with a given [url] in full screen
|
/// Show an image with a given [url] in full screen
|
||||||
void showImageFullScreen(BuildContext context, String url) {
|
void showImageFullScreen(BuildContext context, String url) {
|
||||||
Navigator.of(context).push(MaterialPageRoute(builder: (c) {
|
Navigator.of(context).push(MaterialPageRoute(builder: (c) {
|
||||||
|
|
||||||
// TODO : add better support later
|
// TODO : add better support later
|
||||||
return CachedNetworkImage(
|
return CachedNetworkImage(
|
||||||
imageUrl: url,
|
imageUrl: url,
|
||||||
);
|
);
|
||||||
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Show simple snack
|
/// Show simple snack
|
||||||
void showSimpleSnack(BuildContext context, String message) {
|
void showSimpleSnack(BuildContext context, String message) {
|
||||||
Scaffold.of(context).showSnackBar(SnackBar(content: Text(message)));
|
Scaffold.of(context).showSnackBar(SnackBar(content: Text(message)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Show an alert dialog to ask the user to enter a string
|
||||||
|
Future<String> askUserString({
|
||||||
|
@required BuildContext context,
|
||||||
|
@required String title,
|
||||||
|
@required String message,
|
||||||
|
@required String defaultValue,
|
||||||
|
@required String hint,
|
||||||
|
}) async {
|
||||||
|
assert(context != null);
|
||||||
|
assert(title != null);
|
||||||
|
assert(message != null);
|
||||||
|
assert(defaultValue != null);
|
||||||
|
assert(hint != null);
|
||||||
|
|
||||||
|
TextEditingController controller = TextEditingController(text: defaultValue);
|
||||||
|
|
||||||
|
final confirm = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (c) => AlertDialog(
|
||||||
|
title: Text(title),
|
||||||
|
content: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Text(message),
|
||||||
|
TextField(
|
||||||
|
controller: controller,
|
||||||
|
maxLines: null,
|
||||||
|
maxLength: 200,
|
||||||
|
keyboardType: TextInputType.text,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: hint,
|
||||||
|
alignLabelWithHint: true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
FlatButton(
|
||||||
|
child: Text(tr("Cancel").toUpperCase()),
|
||||||
|
onPressed: () => Navigator.pop(c, false),
|
||||||
|
),
|
||||||
|
FlatButton(
|
||||||
|
child: Text(tr("OK")),
|
||||||
|
onPressed: () => Navigator.pop(c, true),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
|
||||||
|
if (confirm == null || !confirm) return null;
|
||||||
|
|
||||||
|
return controller.text;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user