diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/QuickMedia.cpp | 76 | ||||
-rw-r--r-- | src/plugins/Matrix.cpp | 54 |
2 files changed, 90 insertions, 40 deletions
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"); } } |