From 9bf163d51a252fb5a611e88c2e0b4123a98169e1 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sat, 17 Oct 2020 09:40:10 +0200 Subject: Matrix: show reply messages embedded in messages that reply to them, like element does --- src/QuickMedia.cpp | 148 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 111 insertions(+), 37 deletions(-) (limited to 'src/QuickMedia.cpp') diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 80567f2..bd4b2d4 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -2872,27 +2872,45 @@ namespace QuickMedia { } } + static std::string remove_reply_formatting(const std::string &str) { + if(strncmp(str.c_str(), "> <@", 4) == 0) { + size_t index = str.find("> ", 4); + if(index != std::string::npos) { + size_t msg_begin = str.find("\n\n", index + 2); + if(msg_begin != std::string::npos) + return str.substr(msg_begin + 2); + } + } + return str; + } + + static std::shared_ptr message_to_body_item(Message *message) { + auto body_item = BodyItem::create(""); + body_item->set_author(message->user->display_name); + std::string text = message->body; + if(message->related_event_type == RelatedEventType::REPLY) + text = remove_reply_formatting(text); + body_item->set_description(std::move(text)); + body_item->set_timestamp(message->timestamp); + if(!message->thumbnail_url.empty()) + body_item->thumbnail_url = message->thumbnail_url; + else if(!message->url.empty() && message->type == MessageType::IMAGE) + body_item->thumbnail_url = message->url; + else + body_item->thumbnail_url = message->user->avatar_url; + // TODO: Show image thumbnail inline instead of url to image and showing it as the thumbnail of the body item + body_item->url = message->url; + body_item->author_color = message->user->display_name_color; + body_item->userdata = (void*)message; // Note: message has to be valid as long as body_item is used! + if(message->related_event_type == RelatedEventType::REDACTION || message->related_event_type == RelatedEventType::EDIT) + body_item->visible = false; + return body_item; + } + static BodyItems messages_to_body_items(const Messages &messages) { BodyItems result_items(messages.size()); for(size_t i = 0; i < messages.size(); ++i) { - auto &message = messages[i]; - auto body_item = BodyItem::create(""); - body_item->set_author(message->user->display_name); - body_item->set_description(message->body); - body_item->set_timestamp(message->timestamp); - if(!message->thumbnail_url.empty()) - body_item->thumbnail_url = message->thumbnail_url; - else if(!message->url.empty() && message->type == MessageType::IMAGE) - body_item->thumbnail_url = message->url; - else - body_item->thumbnail_url = message->user->avatar_url; - // TODO: Show image thumbnail inline instead of url to image and showing it as the thumbnail of the body item - body_item->url = message->url; - body_item->author_color = message->user->display_name_color; - body_item->userdata = (void*)message.get(); // Note: message has to be valid as long as body_item is used! - result_items[i] = std::move(body_item); - if(message->related_event_type == RelatedEventType::REDACTION || message->related_event_type == RelatedEventType::EDIT) - result_items[i]->visible = false; + result_items[i] = message_to_body_item(messages[i].get()); } return result_items; } @@ -3092,6 +3110,50 @@ namespace QuickMedia { bool fetching_previous_messages_running = false; std::shared_ptr previous_messages_future_room; + std::future> fetch_reply_message_future; + bool fetching_reply_message_running = false; + std::shared_ptr fetch_reply_future_room; + BodyItem *fetch_reply_body_item = nullptr; + + // TODO: Optimize with hash map? + auto find_body_item_by_event_id = [](std::shared_ptr *body_items, size_t num_body_items, const std::string &event_id) -> std::shared_ptr { + for(size_t i = 0; i < num_body_items; ++i) { + auto &body_item = body_items[i]; + if(static_cast(body_item->userdata)->event_id == event_id) + return body_item; + } + return nullptr; + }; + + // TODO: How about instead fetching all messages we have, not only the visible ones? also fetch with multiple threads. + // TODO: Cancel when going to another room? + tabs[MESSAGES_TAB_INDEX].body->body_item_render_callback = [this, ¤t_room, &fetch_reply_message_future, &tabs, &find_body_item_by_event_id, &fetching_reply_message_running, &fetch_reply_future_room, &fetch_reply_body_item](BodyItem *body_item) { + if(fetching_reply_message_running || !current_room) + return; + + Message *message = static_cast(body_item->userdata); + if(message->related_event_id.empty() || body_item->embedded_item_status != EmbeddedItemStatus::NONE) + return; + + // Check if we already have the referenced message as a body item, so we dont create a new one + auto related_body_item = find_body_item_by_event_id(tabs[MESSAGES_TAB_INDEX].body->items.data(), tabs[MESSAGES_TAB_INDEX].body->items.size(), message->related_event_id); + if(related_body_item) { + body_item->embedded_item = related_body_item; + body_item->embedded_item_status = EmbeddedItemStatus::FINISHED_LOADING; + return; + } + + fetching_reply_message_running = true; + std::string message_event_id = message->related_event_id; + fetch_reply_future_room = current_room; + fetch_reply_body_item = body_item; + body_item->embedded_item_status = EmbeddedItemStatus::LOADING; + // TODO: Check if the message is already cached before calling async? is this needed? is async creation expensive? + fetch_reply_message_future = std::async(std::launch::async, [this, &fetch_reply_future_room, message_event_id]() { + return matrix->get_message_by_id(fetch_reply_future_room.get(), message_event_id); + }); + }; + const float tab_spacer_height = 0.0f; sf::Vector2f body_pos; sf::Vector2f body_size; @@ -3220,7 +3282,7 @@ namespace QuickMedia { auto add_new_messages_to_current_room = [&tabs](Messages &messages) { int num_items = tabs[MESSAGES_TAB_INDEX].body->items.size(); - bool scroll_to_end = (num_items == 0 || (num_items > 0 && tabs[MESSAGES_TAB_INDEX].body->get_selected_item() == num_items - 1)); + bool scroll_to_end = (num_items == 0 || tabs[MESSAGES_TAB_INDEX].body->is_selected_item_last_visible_item()); BodyItem *selected_item = tabs[MESSAGES_TAB_INDEX].body->get_selected(); tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps(messages_to_body_items(messages)); @@ -3269,28 +3331,21 @@ namespace QuickMedia { return nullptr; }; - // TODO: Optimize with hash map? - auto find_body_items_by_event_id = [](std::shared_ptr *body_items, size_t num_body_items, const std::string &event_id) -> std::shared_ptr { - for(size_t i = 0; i < num_body_items; ++i) { - auto &body_item = body_items[i]; - if(static_cast(body_item->userdata)->event_id == event_id) - return body_item; - } - return nullptr; - }; - // TODO: What if these never end up referencing events? clean up automatically after a while? std::unordered_map, Messages> unreferenced_event_by_room; - auto resolve_unreferenced_events_with_body_items = [&unreferenced_event_by_room, ¤t_room, &find_body_items_by_event_id](std::shared_ptr *body_items, size_t num_body_items) { + // TODO: Optimize with hash map? + auto resolve_unreferenced_events_with_body_items = [&unreferenced_event_by_room, ¤t_room, &find_body_item_by_event_id](std::shared_ptr *body_items, size_t num_body_items) { auto &unreferenced_events = unreferenced_event_by_room[current_room]; for(auto it = unreferenced_events.begin(); it != unreferenced_events.end(); ) { auto &message = *it; // TODO: Make redacted/edited events as (redacted)/(edited) in the body if(message->related_event_type == RelatedEventType::REDACTION || message->related_event_type == RelatedEventType::EDIT) { - auto body_item = find_body_items_by_event_id(body_items, num_body_items, message->related_event_id); + auto body_item = find_body_item_by_event_id(body_items, num_body_items, message->related_event_id); if(body_item) { body_item->set_description(message->body); + if(message->related_event_type == RelatedEventType::REDACTION) + body_item->thumbnail_url = message->user->avatar_url; it = unreferenced_events.erase(it); } else { ++it; @@ -3301,17 +3356,21 @@ namespace QuickMedia { } }; - auto modify_related_messages_in_current_room = [&unreferenced_event_by_room, ¤t_room, &find_body_items_by_event_id, &tabs](Messages &messages) { + // TODO: Optimize with hash map? + auto modify_related_messages_in_current_room = [&unreferenced_event_by_room, ¤t_room, &find_body_item_by_event_id, &tabs](Messages &messages) { auto &unreferenced_events = unreferenced_event_by_room[current_room]; + auto &body_items = tabs[MESSAGES_TAB_INDEX].body->items; for(auto &message : messages) { // TODO: Make redacted/edited events as (redacted)/(edited) in the body if(message->related_event_type == RelatedEventType::REDACTION || message->related_event_type == RelatedEventType::EDIT) { - auto &body_items = tabs[MESSAGES_TAB_INDEX].body->items; - auto body_item = find_body_items_by_event_id(body_items.data(), body_items.size(), message->related_event_id); - if(body_item) + auto body_item = find_body_item_by_event_id(body_items.data(), body_items.size(), message->related_event_id); + if(body_item) { body_item->set_description(message->body); - else + if(message->related_event_type == RelatedEventType::REDACTION) + body_item->thumbnail_url = message->user->avatar_url; + } else { unreferenced_events.push_back(message); + } } } }; @@ -3770,7 +3829,7 @@ namespace QuickMedia { fprintf(stderr, "Finished fetching older messages, num new messages: %zu\n", new_messages.size()); // Ignore finished fetch of messages if it happened in another room. When we navigate back to the room we will get the messages again size_t num_new_messages = new_messages.size(); - if(previous_messages_future_room == current_room && num_new_messages > 0) { + if(num_new_messages > 0 && previous_messages_future_room == current_room) { BodyItem *selected_item = tabs[MESSAGES_TAB_INDEX].body->get_selected(); BodyItems new_body_items = messages_to_body_items(new_messages); size_t num_new_body_items = new_body_items.size(); @@ -3786,6 +3845,21 @@ namespace QuickMedia { fetching_previous_messages_running = false; } + if(fetching_reply_message_running && fetch_reply_message_future.valid() && fetch_reply_message_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { + std::shared_ptr replied_to_message = fetch_reply_message_future.get(); + fprintf(stderr, "Finished fetching reply to message: %s\n", replied_to_message ? replied_to_message->event_id.c_str() : "(null)"); + // Ignore finished fetch of messages if it happened in another room. When we navigate back to the room we will get the messages again + if(fetch_reply_future_room == current_room) { + if(replied_to_message) { + fetch_reply_body_item->embedded_item = message_to_body_item(replied_to_message.get()); + fetch_reply_body_item->embedded_item_status = EmbeddedItemStatus::FINISHED_LOADING; + } else { + fetch_reply_body_item->embedded_item_status = EmbeddedItemStatus::FAILED_TO_LOAD; + } + } + fetching_reply_message_running = false; + } + //chat_input.update(); window.clear(back_color); -- cgit v1.2.3