From 0b6f4abda7eb9696ada9c6cf0da54499fe1a0e53 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Mon, 19 Oct 2020 06:59:51 +0200 Subject: Matrix: add room search, move rooms with mentions/unread messages to top --- src/Body.cpp | 10 +- src/QuickMedia.cpp | 336 +++++++++++++++++++++++++++++++---------------------- 2 files changed, 201 insertions(+), 145 deletions(-) (limited to 'src') diff --git a/src/Body.cpp b/src/Body.cpp index fe57130..e60a691 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -774,14 +774,18 @@ namespace QuickMedia { } for(auto &item : items) { - item->visible = string_find_case_insensitive(item->get_title(), text); - if(!item->visible && !item->get_description().empty()) - item->visible = string_find_case_insensitive(item->get_description(), text); + filter_search_fuzzy_item(text, item.get()); } select_first_item(); } + void Body::filter_search_fuzzy_item(const std::string &text, BodyItem *body_item) { + body_item->visible = string_find_case_insensitive(body_item->get_title(), text); + if(!body_item->visible && !body_item->get_description().empty()) + body_item->visible = string_find_case_insensitive(body_item->get_description(), text); + } + bool Body::no_items_visible() const { for(auto &item : items) { if(item->visible) diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 625a426..dc7e6f3 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -2978,8 +2978,13 @@ namespace QuickMedia { bool is_window_focused = window.hasFocus(); - auto process_new_room_messages = [this, &body_items_by_room, ¤t_room, &is_window_focused](RoomSyncMessages &room_sync_messages, bool only_show_mentions) mutable { + // TODO: Always keep the mentioned rooms at the top, and then the ones with unread messages below? + auto process_new_room_messages = [this, &tabs, &body_items_by_room, ¤t_room, &is_window_focused](RoomSyncMessages &room_sync_messages, bool is_first_sync) mutable { + size_t room_swap_index = 0; for(auto &[room, messages] : room_sync_messages) { + if(messages.empty()) + continue; + bool was_mentioned = false; for(auto &message : messages) { if(message->mentions_me) { @@ -2996,31 +3001,28 @@ namespace QuickMedia { continue; // 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. - // TODO: Update local marker when another client with our user sets read marker, in that case our read marker (room->get_user_read_marker) will be updated. - bool unread_messages_previous_session = false; - if(!messages.empty()) { - std::shared_ptr me = matrix->get_me(room); - if(me && room->get_user_read_marker(me) != messages.back()->event_id) - unread_messages_previous_session = true; - } - - if(only_show_mentions && !unread_messages_previous_session) { - std::string room_desc; - if(!messages.empty()) - room_desc = matrix->message_get_author_displayname(messages.back().get()) + ": " + extract_first_line(messages.back()->body, 150); - if(was_mentioned) { - room_desc += "\n** You were mentioned **"; // TODO: Better notification? - room_body_item_it->second.body_item->set_title_color(sf::Color(255, 100, 100)); - room_body_item_it->second.last_message_read = false; - } - room_body_item_it->second.body_item->set_description(std::move(room_desc)); - } else if(!messages.empty()) { + bool unread_messages = false; + std::shared_ptr me = matrix->get_me(room); + if(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) room_desc += "\n** You were mentioned **"; // TODO: Better notification? room_body_item_it->second.body_item->set_description(std::move(room_desc)); room_body_item_it->second.body_item->set_title_color(sf::Color(255, 100, 100)); room_body_item_it->second.last_message_read = false; + + // Swap room with the top one + // 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_it->second.body_item.get()); + 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; + } + } else if(is_first_sync) { + room_body_item_it->second.body_item->set_description(matrix->message_get_author_displayname(messages.back().get()) + ": " + extract_first_line(messages.back()->body, 150)); } } }; @@ -3041,6 +3043,141 @@ namespace QuickMedia { sf::Sprite logo_sprite(plugin_logo); + sf::Text room_name_text("", *bold_font, 18); + const float room_name_text_height = 20.0f; + const float room_name_text_padding_y = 10.0f; + const float room_name_total_height = room_name_text_height + room_name_text_padding_y * 2.0f; + const float room_avatar_height = 32.0f; + + sf::Sprite room_avatar_sprite; + auto room_avatar_thumbnail_data = std::make_shared(); + AsyncImageLoader async_image_loader; + + sf::Clock read_marker_timer; + const sf::Int32 read_marker_timeout_ms_default = 3000; + sf::Int32 read_marker_timeout_ms = 0; + + std::future set_read_marker_future; + bool setting_read_marker = false; + + bool redraw = true; + + auto get_room_by_ptr = [&body_items_by_room](RoomData *room_ptr) -> std::shared_ptr { + for(auto &[room, body_data] : body_items_by_room) { + if(room.get() == room_ptr) + return room; + } + return 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: What if these never end up referencing events? clean up automatically after a while? + std::unordered_map, Messages> unreferenced_event_by_room; + + auto set_body_as_deleted = [](Message *message, BodyItem *body_item) { + body_item->embedded_item = nullptr; + body_item->embedded_item_status = EmbeddedItemStatus::NONE; + body_item->thumbnail_url = message->user->avatar_url; + body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE; + body_item->set_description_color(sf::Color::White); + body_item->thumbnail_size = sf::Vector2i(32, 32); + }; + + // TODO: Optimize with hash map? + auto resolve_unreferenced_events_with_body_items = [&set_body_as_deleted, &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_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) + set_body_as_deleted(message.get(), body_item.get()); + it = unreferenced_events.erase(it); + } else { + ++it; + } + } else { + ++it; + } + } + }; + + // TODO: Optimize with hash map? + auto modify_related_messages_in_current_room = [&set_body_as_deleted, &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_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); + if(message->related_event_type == RelatedEventType::REDACTION) + set_body_as_deleted(message.get(), body_item.get()); + } else { + unreferenced_events.push_back(message); + } + } + } + }; + + SearchBar room_search_bar(*font, &plugin_logo, "Search..."); + room_search_bar.autocomplete_search_delay = SEARCH_DELAY_FILTER; + room_search_bar.onTextUpdateCallback = [&tabs](const std::string &text) { + tabs[ROOMS_TAB_INDEX].body->filter_search_fuzzy(text); + //tabs[ROOMS_TAB_INDEX].body->select_first_item(); + }; + + room_search_bar.onTextSubmitCallback = + [this, &tabs, &selected_tab, ¤t_room, ¤t_room_body_data, &room_name_text, + &get_room_by_ptr, &body_items_by_room, &modify_related_messages_in_current_room, + &room_avatar_thumbnail_data, &read_marker_timeout_ms, &redraw] + (const std::string&) + { + BodyItem *selected_item = tabs[ROOMS_TAB_INDEX].body->get_selected(); + if(!selected_item) + return; + + tabs[ROOMS_TAB_INDEX].body->clear_cache(); + + current_room = get_room_by_ptr((RoomData*)selected_item->userdata); + assert(current_room); + selected_tab = MESSAGES_TAB_INDEX; + tabs[MESSAGES_TAB_INDEX].body->clear_items(); + + Messages new_messages; + if(matrix->get_all_synced_room_messages(current_room, new_messages) == PluginResult::OK) { + tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps(messages_to_body_items(new_messages, matrix->get_me(current_room).get())); + tabs[MESSAGES_TAB_INDEX].body->select_last_item(); + modify_related_messages_in_current_room(new_messages); + } else { + std::string err_msg = "Failed to get messages in room: " + current_room->id; + show_notification("QuickMedia", err_msg, Urgency::CRITICAL); + } + + auto room_body_item_it = body_items_by_room.find(current_room); + if(room_body_item_it != body_items_by_room.end()) { + current_room_body_data = &room_body_item_it->second; + room_name_text.setString(current_room_body_data->body_item->get_title()); + room_avatar_thumbnail_data = std::make_shared(); + } + + read_marker_timeout_ms = 0; + redraw = true; + }; + Entry chat_input("Press m or i to begin writing a message...", font.get(), cjk_font.get()); chat_input.draw_background = false; chat_input.set_editable(false); @@ -3129,16 +3266,6 @@ namespace QuickMedia { 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) { @@ -3171,7 +3298,6 @@ namespace QuickMedia { const float tab_spacer_height = 0.0f; sf::Vector2f body_pos; sf::Vector2f body_size; - bool redraw = true; sf::Event event; sf::RectangleShape tab_shade; @@ -3194,17 +3320,7 @@ namespace QuickMedia { const double typing_timeout_seconds = 3.0; bool typing = false; - const float tab_vertical_offset = 10.0f; - - sf::Text room_name_text("", *bold_font, 18); - const float room_name_text_height = 20.0f; - const float room_name_text_padding_y = 10.0f; - const float room_name_total_height = room_name_text_height + room_name_text_padding_y * 2.0f; - const float room_avatar_height = 32.0f; - - sf::Sprite room_avatar_sprite; - auto room_avatar_thumbnail_data = std::make_shared(); - AsyncImageLoader async_image_loader; + float tab_vertical_offset = 0.0f; auto typing_async_func = [this](bool new_state, std::shared_ptr room) { if(new_state) { @@ -3225,13 +3341,6 @@ namespace QuickMedia { Body url_selection_body(this, font.get(), bold_font.get(), cjk_font.get()); - sf::Clock read_marker_timer; - const sf::Int32 read_marker_timeout_ms_default = 3000; - sf::Int32 read_marker_timeout_ms = 0; - - std::future set_read_marker_future; - bool setting_read_marker = false; - auto launch_url = [this, &video_page, &redraw](const std::string &url) mutable { if(url.empty()) return; @@ -3276,10 +3385,12 @@ namespace QuickMedia { } }; - auto add_new_rooms = [&tabs, &body_items_by_room, ¤t_room, ¤t_room_body_data, &room_name_text](Rooms &rooms) { + auto add_new_rooms = [&tabs, &body_items_by_room, ¤t_room, ¤t_room_body_data, &room_name_text, &room_search_bar](Rooms &rooms) { if(rooms.empty()) return; + std::string search_filter_text = room_search_bar.get_text(); + for(size_t i = 0; i < rooms.size(); ++i) { auto &room = rooms[i]; std::string room_name = room->name; @@ -3291,6 +3402,7 @@ namespace QuickMedia { body_item->userdata = room.get(); // Note: this has to be valid as long as the room list is valid! body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE; body_item->thumbnail_size = sf::Vector2i(32, 32); + tabs[ROOMS_TAB_INDEX].body->filter_search_fuzzy_item(search_filter_text, body_item.get()); tabs[ROOMS_TAB_INDEX].body->items.push_back(body_item); body_items_by_room[room] = { body_item, true, 0 }; } @@ -3306,71 +3418,19 @@ namespace QuickMedia { } }; - auto get_room_by_ptr = [&body_items_by_room](RoomData *room_ptr) -> std::shared_ptr { - for(auto &[room, body_data] : body_items_by_room) { - if(room.get() == room_ptr) - return room; - } - 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 set_body_as_deleted = [](Message *message, BodyItem *body_item) { - body_item->embedded_item = nullptr; - body_item->embedded_item_status = EmbeddedItemStatus::NONE; - body_item->thumbnail_url = message->user->avatar_url; - body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE; - body_item->set_description_color(sf::Color::White); - body_item->thumbnail_size = sf::Vector2i(32, 32); - }; - - // TODO: Optimize with hash map? - auto resolve_unreferenced_events_with_body_items = [&set_body_as_deleted, &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_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) - set_body_as_deleted(message.get(), body_item.get()); - it = unreferenced_events.erase(it); - } else { - ++it; - } - } else { - ++it; - } - } - }; - - // TODO: Optimize with hash map? - auto modify_related_messages_in_current_room = [&set_body_as_deleted, &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_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); - if(message->related_event_type == RelatedEventType::REDACTION) - set_body_as_deleted(message.get(), body_item.get()); - } else { - unreferenced_events.push_back(message); - } - } - } - }; + float tab_shade_height = 0.0f; while (current_page == PageType::CHAT) { sf::Int32 frame_time_ms = frame_timer.restart().asMilliseconds(); while (window.pollEvent(event)) { base_event_handler(event, PageType::EXIT, tabs[selected_tab].body.get(), nullptr, false, false); + + if(tabs[selected_tab].type == ChatTabType::ROOMS) { + if(event.type == sf::Event::TextEntered) + room_search_bar.onTextEntered(event.text.unicode); + room_search_bar.on_event(event); + } + if(event.type == sf::Event::GainedFocus) { is_window_focused = true; redraw = true; @@ -3610,36 +3670,6 @@ namespace QuickMedia { } //chat_input.on_event(event); chat_input.process_event(event); - } else if(tabs[selected_tab].type == ChatTabType::ROOMS && event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Enter) { - BodyItem *selected_item = tabs[selected_tab].body->get_selected(); - if(selected_item) { - tabs[selected_tab].body->clear_cache(); - - current_room = get_room_by_ptr((RoomData*)selected_item->userdata); - assert(current_room); - selected_tab = MESSAGES_TAB_INDEX; - tabs[MESSAGES_TAB_INDEX].body->clear_items(); - - Messages new_messages; - if(matrix->get_all_synced_room_messages(current_room, new_messages) == PluginResult::OK) { - tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps(messages_to_body_items(new_messages, matrix->get_me(current_room).get())); - tabs[MESSAGES_TAB_INDEX].body->select_last_item(); - modify_related_messages_in_current_room(new_messages); - } else { - std::string err_msg = "Failed to get messages in room: " + current_room->id; - show_notification("QuickMedia", err_msg, Urgency::CRITICAL); - } - - auto room_body_item_it = body_items_by_room.find(current_room); - if(room_body_item_it != body_items_by_room.end()) { - current_room_body_data = &room_body_item_it->second; - room_name_text.setString(current_room_body_data->body_item->get_title()); - room_avatar_thumbnail_data = std::make_shared(); - } - - read_marker_timeout_ms = 0; - redraw = true; - } } } @@ -3735,8 +3765,11 @@ namespace QuickMedia { } } - const float room_name_padding_y = (selected_tab == MESSAGES_TAB_INDEX ? room_name_total_height : 0.0f); - const float tab_shade_height = tab_spacer_height + std::floor(tab_vertical_offset) + tab_height + 10.0f + room_name_padding_y; + float room_name_padding_y = 0.0f; + if(selected_tab == MESSAGES_TAB_INDEX) + room_name_padding_y = room_name_total_height; + else if(selected_tab == ROOMS_TAB_INDEX) + room_name_padding_y = room_search_bar.getBottomWithoutShadow(); chat_input_height_full = chat_input.get_height() + chat_input_padding_y * 2.0f; if(selected_tab != MESSAGES_TAB_INDEX) @@ -3750,6 +3783,21 @@ namespace QuickMedia { if(redraw) { redraw = false; + room_search_bar.onWindowResize(window_size); + + float room_name_padding_y = 0.0f; + float padding_bottom = 0.0f; + if(selected_tab == MESSAGES_TAB_INDEX) { + room_name_padding_y = 10.0f + room_name_total_height; + tab_vertical_offset = 10.0f; + } else if(selected_tab == ROOMS_TAB_INDEX) { + room_name_padding_y = room_search_bar.getBottomWithoutShadow(); + tab_vertical_offset = 0.0f; + padding_bottom = 10.0f; + } + + tab_shade_height = tab_spacer_height + std::floor(tab_vertical_offset) + tab_height + room_name_padding_y + padding_bottom; + chat_input.set_max_width(window_size.x - (logo_padding_x + plugin_logo.getSize().x + chat_input_padding_x * 2.0f)); chat_input.set_position(sf::Vector2f(logo_padding_x + plugin_logo.getSize().x + chat_input_padding_x, window_size.y - chat_height - chat_input_padding_y)); @@ -3773,6 +3821,8 @@ namespace QuickMedia { logo_sprite.setPosition(logo_padding_x, window_size.y - chat_input_shade.getSize().y * 0.5f - plugin_logo.getSize().y * 0.5f); } + room_search_bar.update(); + if(!sync_running && sync_timer.getElapsedTime().asMilliseconds() >= sync_min_time_ms) { fprintf(stderr, "Time since last sync: %d ms\n", sync_timer.getElapsedTime().asMilliseconds()); sync_min_time_ms = 1000; @@ -3877,6 +3927,8 @@ namespace QuickMedia { } room_name_text.setPosition(body_pos.x + room_name_text_offset_x, room_name_text_padding_y + 4.0f); window.draw(room_name_text); + } else if(tabs[selected_tab].type == ChatTabType::ROOMS) { + room_search_bar.draw(window, false); } gradient_points[0].position.x = 0.0f; -- cgit v1.2.3