diff options
author | dec05eba <dec05eba@protonmail.com> | 2020-10-21 04:57:21 +0200 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2020-10-21 04:57:21 +0200 |
commit | f8b54139f89ef21eb025defd17a5655e41fb9c03 (patch) | |
tree | a29540ddb94df59ede1700549d536536188ff2bf | |
parent | ea6f1b425a6aa9b7145a00d81473ef9508279eec (diff) |
Matrix: sort rooms by mention/unread messages
fix multiple messages being marker as mentioning us if the latest
mention is close to another mention that we have already read.
Do not go to bottom when sending a message or uploading media.
Do not scroll to bottom unless we have the last message selected, the
tab is the messages tab and the window is focused.
-rw-r--r-- | TODO | 4 | ||||
-rw-r--r-- | plugins/Matrix.hpp | 4 | ||||
-rw-r--r-- | src/QuickMedia.cpp | 76 | ||||
-rw-r--r-- | src/plugins/Matrix.cpp | 54 |
4 files changed, 95 insertions, 43 deletions
@@ -107,4 +107,6 @@ Ignore timestamp ordering for messages in matrix? element seems to do that (or o Merge |Page::search| and |Page::get_page|. get_page with page 0 should be the same as search. Disable posting in 4chan thread if closed (thread json contains "closed" field for OP). Remove calls to get the original message of an edit in edits and replies in matrix if possible. These calls take additional time, and with a slow homeserver or high ping this could make messages to be delayed by an annoying amount of time. -Read image exif into to apply image rotation. This is common in images taken on phones. If not done, the width and height will also be mixed and thumbnail fallback size will be incorrectly calculated (for example in matrix).
\ No newline at end of file +Read image exif into to apply image rotation. This is common in images taken on phones. If not done, the width and height will also be mixed and thumbnail fallback size will be incorrectly calculated (for example in matrix). +Handle M_LIMIT_EXCEEDED in matrix +Check if we need to call /_matrix/client/r0/notifications for intial sync to receive old notifications or if /sync always includes mentions.
\ No newline at end of file diff --git a/plugins/Matrix.hpp b/plugins/Matrix.hpp index a48cf69..f24a08f 100644 --- a/plugins/Matrix.hpp +++ b/plugins/Matrix.hpp @@ -74,9 +74,9 @@ namespace QuickMedia { std::string get_user_read_marker(std::shared_ptr<UserInfo> &user); // Ignores duplicates - void prepend_messages_reverse(std::vector<std::shared_ptr<Message>> new_messages); + void prepend_messages_reverse(const std::vector<std::shared_ptr<Message>> &new_messages); // Ignores duplicates - void append_messages(std::vector<std::shared_ptr<Message>> new_messages); + void append_messages(const std::vector<std::shared_ptr<Message>> &new_messages); std::shared_ptr<Message> get_message_by_id(const std::string &id); diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 90abfcc..0021f96 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -2967,22 +2967,44 @@ namespace QuickMedia { RoomData *current_room = nullptr; bool is_window_focused = window.hasFocus(); - auto process_new_room_messages = [this, &selected_tab, ¤t_room, &is_window_focused](RoomSyncMessages &room_sync_messages, bool is_first_sync) mutable { - for(auto &[room, messages] : room_sync_messages) { - if(messages.empty()) - continue; + // Returns -1 if no rooms or no unread rooms + auto find_top_body_position_for_unread_room = [&tabs](BodyItem *item_to_swap, int start_index) { + for(int i = start_index; i < (int)tabs[ROOMS_TAB_INDEX].body->items.size(); ++i) { + const auto &body_item = tabs[ROOMS_TAB_INDEX].body->items[i]; + if(static_cast<RoomData*>(body_item->userdata)->last_message_read || body_item.get() == item_to_swap) + return i; + } + return -1; + }; - bool was_mentioned = false; + // Returns -1 if no rooms or all rooms have unread mentions + auto find_top_body_position_for_mentioned_room = [&tabs](BodyItem *item_to_swap, int start_index) { + for(int i = start_index; i < (int)tabs[ROOMS_TAB_INDEX].body->items.size(); ++i) { + const auto &body_item = tabs[ROOMS_TAB_INDEX].body->items[i]; + if(!static_cast<RoomData*>(body_item->userdata)->has_unread_mention || body_item.get() == item_to_swap) + return i; + } + return -1; + }; + + auto process_new_room_messages = + [this, &selected_tab, ¤t_room, &is_window_focused, &tabs, &find_top_body_position_for_unread_room, &find_top_body_position_for_mentioned_room] + (RoomSyncMessages &room_sync_messages, bool is_first_sync) mutable + { + for(auto &[room, messages] : room_sync_messages) { for(auto &message : messages) { if(message->mentions_me) { - was_mentioned = true; room->has_unread_mention = true; - //message->mentions_me = false; // TODO: What if the message or username begins with "-"? also make the notification image be the avatar of the user if(!is_window_focused || room != current_room || is_first_sync || selected_tab == ROOMS_TAB_INDEX) show_notification("QuickMedia matrix - " + matrix->message_get_author_displayname(message.get()) + " (" + room->name + ")", message->body); } } + } + + for(auto &[room, messages] : room_sync_messages) { + if(messages.empty()) + continue; BodyItem *room_body_item = static_cast<BodyItem*>(room->userdata); assert(room_body_item); @@ -2990,27 +3012,41 @@ namespace QuickMedia { // TODO: this wont always work because we dont display all types of messages from server, such as "joined", "left", "kicked", "banned", "changed avatar", "changed display name", etc. bool unread_messages = false; std::shared_ptr<UserInfo> me = matrix->get_me(room); - if(me && room->get_user_read_marker(me) != messages.back()->event_id) + if(room->has_unread_mention || (me && room->get_user_read_marker(me) != messages.back()->event_id)) unread_messages = true; if(unread_messages) { std::string room_desc = "Unread: " + matrix->message_get_author_displayname(messages.back().get()) + ": " + extract_first_line(messages.back()->body, 150); - if(was_mentioned) + if(room->has_unread_mention) room_desc += "\n** You were mentioned **"; // TODO: Better notification? room_body_item->set_description(std::move(room_desc)); room_body_item->set_title_color(sf::Color(255, 100, 100)); room->last_message_read = false; - // Swap room with the top one + // Swap order of rooms in body list to put rooms with mentions at the top and then unread messages and then all the other rooms // TODO: Optimize with hash map instead of linear search? or cache the index - // int room_body_index = tabs[ROOMS_TAB_INDEX].body->get_index_by_body_item(room_body_item); - // if(room_body_index != -1) { - // std::swap(tabs[ROOMS_TAB_INDEX].body->items[room_body_index], tabs[ROOMS_TAB_INDEX].body->items[room_swap_index]); - // ++room_swap_index; - // } + int room_body_index = tabs[ROOMS_TAB_INDEX].body->get_index_by_body_item(room_body_item); + if(room_body_index != -1) { + int body_swap_index = 0; + while(true) { + BodyItem *body_item = tabs[ROOMS_TAB_INDEX].body->items[room_body_index].get(); + RoomData *room = static_cast<RoomData*>(body_item->userdata); + if(room->has_unread_mention) + body_swap_index = find_top_body_position_for_mentioned_room(body_item, body_swap_index); + else if(!room->last_message_read) + body_swap_index = find_top_body_position_for_unread_room(body_item, body_swap_index); + else + break; + + if(body_swap_index == -1) + break; + + std::swap(tabs[ROOMS_TAB_INDEX].body->items[room_body_index], tabs[ROOMS_TAB_INDEX].body->items[body_swap_index]); + ++body_swap_index; + } + } } else if(is_first_sync) { room_body_item->set_description(matrix->message_get_author_displayname(messages.back().get()) + ": " + extract_first_line(messages.back()->body, 150)); - room->has_unread_mention = false; } } }; @@ -3184,8 +3220,6 @@ namespace QuickMedia { } } - tabs[selected_tab].body->select_last_item(); - if(chat_state == ChatState::TYPING_MESSAGE) { // TODO: Make asynchronous if(matrix->post_message(current_room, text, std::nullopt, std::nullopt) == PluginResult::OK) { @@ -3346,9 +3380,11 @@ namespace QuickMedia { } }; - auto add_new_messages_to_current_room = [this, &tabs, ¤t_room](Messages &messages) { + auto add_new_messages_to_current_room = [this, &tabs, &selected_tab, ¤t_room, &is_window_focused](Messages &messages) { int num_items = tabs[MESSAGES_TAB_INDEX].body->items.size(); - bool scroll_to_end = (num_items == 0 || tabs[MESSAGES_TAB_INDEX].body->is_selected_item_last_visible_item()); + bool scroll_to_end = num_items == 0; + if(tabs[MESSAGES_TAB_INDEX].body->is_selected_item_last_visible_item() && selected_tab == MESSAGES_TAB_INDEX && is_window_focused) + scroll_to_end = true; BodyItem *selected_item = tabs[MESSAGES_TAB_INDEX].body->get_selected(); tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps(messages_to_body_items(messages, matrix->get_me(current_room).get())); diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index ff0d498..7de8af9 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -55,7 +55,7 @@ namespace QuickMedia { return user->read_marker_event_id; } - void RoomData::prepend_messages_reverse(std::vector<std::shared_ptr<Message>> new_messages) { + void RoomData::prepend_messages_reverse(const std::vector<std::shared_ptr<Message>> &new_messages) { std::lock_guard<std::mutex> lock(room_mutex); for(auto it = new_messages.begin(); it != new_messages.end(); ++it) { if(message_by_event_id.find((*it)->event_id) == message_by_event_id.end()) { @@ -65,7 +65,7 @@ namespace QuickMedia { } } - void RoomData::append_messages(std::vector<std::shared_ptr<Message>> new_messages) { + void RoomData::append_messages(const std::vector<std::shared_ptr<Message>> &new_messages) { std::lock_guard<std::mutex> lock(room_mutex); for(auto it = new_messages.begin(); it != new_messages.end(); ++it) { if(message_by_event_id.find((*it)->event_id) == message_by_event_id.end()) { @@ -208,6 +208,8 @@ namespace QuickMedia { events_set_room_name(events_json, room); } + const rapidjson::Value &ephemeral_json = GetMember(it.value, "ephemeral"); + const rapidjson::Value &timeline_json = GetMember(it.value, "timeline"); if(timeline_json.IsObject()) { if(room->prev_batch.empty()) { @@ -217,7 +219,7 @@ namespace QuickMedia { room->prev_batch = prev_batch_json.GetString(); } - // TODO: Is there no better way to check for notifications? this is not robust... + // TODO: Use /_matrix/client/r0/notifications ? or remove this and always look for displayname/user_id in messages bool has_unread_notifications = false; const rapidjson::Value &unread_notification_json = GetMember(it.value, "unread_notifications"); if(unread_notification_json.IsObject()) { @@ -228,14 +230,18 @@ namespace QuickMedia { const rapidjson::Value &events_json = GetMember(timeline_json, "events"); events_add_user_info(events_json, room); - events_add_messages(events_json, room, MessageDirection::AFTER, &room_messages, has_unread_notifications); events_set_room_name(events_json, room); - } - - const rapidjson::Value &ephemeral_json = GetMember(it.value, "ephemeral"); - if(ephemeral_json.IsObject()) { - const rapidjson::Value &events_json = GetMember(ephemeral_json, "events"); - events_add_user_read_markers(events_json, room); + // We want to do this before adding messages to know if a message that mentions us is a new mention + if(ephemeral_json.IsObject()) { + const rapidjson::Value &events_json = GetMember(ephemeral_json, "events"); + events_add_user_read_markers(events_json, room); + } + events_add_messages(events_json, room, MessageDirection::AFTER, &room_messages, has_unread_notifications); + } else { + if(ephemeral_json.IsObject()) { + const rapidjson::Value &events_json = GetMember(ephemeral_json, "events"); + events_add_user_read_markers(events_json, room); + } } } @@ -457,25 +463,33 @@ namespace QuickMedia { for(const rapidjson::Value &event_item_json : events_json.GetArray()) { std::shared_ptr<Message> new_message = parse_message_event(event_item_json, room_data); - if(!new_message) - continue; - - // TODO: Is @room ok? shouldn't we also check if the user has permission to do @room? (only when notifications are limited to @mentions) - if(has_unread_notifications && me) - new_message->mentions_me = message_contains_user_mention(new_message->body, me->display_name) || message_contains_user_mention(new_message->body, me->user_id) || message_contains_user_mention(new_message->body, "@room"); - - new_messages.push_back(std::move(new_message)); + if(new_message) + new_messages.push_back(std::move(new_message)); } + if(new_messages.empty()) + return; + // TODO: Add directly to this instead when set? otherwise add to new_messages if(room_messages) (*room_messages)[room_data] = new_messages; // TODO: Loop and std::move instead? doesn't insert create copies? if(message_dir == MessageDirection::BEFORE) { - room_data->prepend_messages_reverse(std::move(new_messages)); + room_data->prepend_messages_reverse(new_messages); } else if(message_dir == MessageDirection::AFTER) { - room_data->append_messages(std::move(new_messages)); + room_data->append_messages(new_messages); + } + + std::shared_ptr<Message> read_marker_message; + if(me) + read_marker_message = room_data->get_message_by_id(room_data->get_user_read_marker(me)); + + for(auto &message : new_messages) { + // TODO: Is @room ok? shouldn't we also check if the user has permission to do @room? (only when notifications are limited to @mentions) + // TODO: Is comparing against read marker timestamp ok enough? + if(has_unread_notifications && me && (!read_marker_message || read_marker_message->timestamp < message->timestamp)) + message->mentions_me = message_contains_user_mention(message->body, me->display_name) || message_contains_user_mention(message->body, me->user_id) || message_contains_user_mention(message->body, "@room"); } } |