1
0
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:
Pierre HUBERT 2019-05-04 10:24:38 +02:00
parent c8b68e71aa
commit 62125d7c3d
5 changed files with 151 additions and 21 deletions

View File

@ -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,

View File

@ -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
}
} }

View File

@ -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

View File

@ -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;
} }
} }
} }

View File

@ -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;
}