aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2020-10-19 06:59:51 +0200
committerdec05eba <dec05eba@protonmail.com>2020-10-19 06:59:51 +0200
commit0b6f4abda7eb9696ada9c6cf0da54499fe1a0e53 (patch)
tree97c845e44b7ee3d62954b54939566e4add08e4b2 /src
parentfd4e8cdc9449dbb0224a1a41ef0561f50012d405 (diff)
Matrix: add room search, move rooms with mentions/unread messages to top
Diffstat (limited to 'src')
-rw-r--r--src/Body.cpp10
-rw-r--r--src/QuickMedia.cpp336
2 files changed, 201 insertions, 145 deletions
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, &current_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, &current_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<UserInfo> 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<UserInfo> 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<ThumbnailData>();
+ 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<void> 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<RoomData> {
+ 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<BodyItem> *body_items, size_t num_body_items, const std::string &event_id) -> std::shared_ptr<BodyItem> {
+ for(size_t i = 0; i < num_body_items; ++i) {
+ auto &body_item = body_items[i];
+ if(static_cast<Message*>(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<std::shared_ptr<RoomData>, 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, &current_room, &find_body_item_by_event_id](std::shared_ptr<BodyItem> *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, &current_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, &current_room, &current_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<ThumbnailData>();
+ }
+
+ 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<RoomData> 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<BodyItem> *body_items, size_t num_body_items, const std::string &event_id) -> std::shared_ptr<BodyItem> {
- for(size_t i = 0; i < num_body_items; ++i) {
- auto &body_item = body_items[i];
- if(static_cast<Message*>(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, &current_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<ThumbnailData>();
- AsyncImageLoader async_image_loader;
+ float tab_vertical_offset = 0.0f;
auto typing_async_func = [this](bool new_state, std::shared_ptr<RoomData> 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<void> 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, &current_room, &current_room_body_data, &room_name_text](Rooms &rooms) {
+ auto add_new_rooms = [&tabs, &body_items_by_room, &current_room, &current_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<RoomData> {
- 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<std::shared_ptr<RoomData>, 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, &current_room, &find_body_item_by_event_id](std::shared_ptr<BodyItem> *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, &current_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<ThumbnailData>();
- }
-
- 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;