From 97564d40636aafb251644f61a0b990e392afd7a4 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Thu, 22 Oct 2020 21:56:33 +0200 Subject: Matrix: add pinned messages tab --- src/QuickMedia.cpp | 221 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 152 insertions(+), 69 deletions(-) (limited to 'src/QuickMedia.cpp') diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index d0a93fe..5c1bdfa 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -2856,13 +2856,7 @@ namespace QuickMedia { } } - enum class ChatTabType { - MESSAGES, - ROOMS - }; - struct ChatTab { - ChatTabType type; std::unique_ptr body; std::future future; sf::Text text; @@ -2934,16 +2928,35 @@ namespace QuickMedia { return result_items; } + enum class PinnedEventStatus { + NONE, + LOADING, + FINISHED_LOADING, + FAILED_TO_LOAD + }; + + struct PinnedEventData { + std::string event_id; + PinnedEventStatus status = PinnedEventStatus::NONE; + }; + void Program::chat_page() { assert(strcmp(plugin_name, "matrix") == 0); auto video_page = std::make_unique(this); std::vector tabs; - int selected_tab = 0; + + ChatTab pinned_tab; + pinned_tab.body = std::make_unique(this, font.get(), bold_font.get(), cjk_font.get(), loading_icon); + pinned_tab.body->draw_thumbnails = true; + pinned_tab.body->thumbnail_max_size = CHAT_MESSAGE_THUMBNAIL_MAX_SIZE; + pinned_tab.body->thumbnail_mask_shader = &circle_mask_shader; + //pinned_tab.body->line_separator_color = sf::Color::Transparent; + pinned_tab.text = sf::Text("Pinned", *font, tab_text_size); + tabs.push_back(std::move(pinned_tab)); ChatTab messages_tab; - messages_tab.type = ChatTabType::MESSAGES; messages_tab.body = std::make_unique(this, font.get(), bold_font.get(), cjk_font.get(), loading_icon); messages_tab.body->draw_thumbnails = true; messages_tab.body->thumbnail_max_size = CHAT_MESSAGE_THUMBNAIL_MAX_SIZE; @@ -2953,7 +2966,6 @@ namespace QuickMedia { tabs.push_back(std::move(messages_tab)); ChatTab rooms_tab; - rooms_tab.type = ChatTabType::ROOMS; rooms_tab.body = std::make_unique(this, font.get(), bold_font.get(), cjk_font.get(), loading_icon); rooms_tab.body->draw_thumbnails = true; //rooms_tab.body->line_separator_color = sf::Color::Transparent; @@ -2961,8 +2973,11 @@ namespace QuickMedia { rooms_tab.text = sf::Text("Rooms", *font, tab_text_size); tabs.push_back(std::move(rooms_tab)); - const int MESSAGES_TAB_INDEX = 0; - const int ROOMS_TAB_INDEX = 1; + const int PINNED_TAB_INDEX = 0; + const int MESSAGES_TAB_INDEX = 1; + const int ROOMS_TAB_INDEX = 2; + + int selected_tab = MESSAGES_TAB_INDEX; // This is needed to get initial data, with joined rooms etc. TODO: Remove this once its cached // and allow asynchronous update of rooms @@ -2992,10 +3007,10 @@ namespace QuickMedia { 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 + (RoomSyncData &room_sync_data, bool is_first_sync) mutable { - for(auto &[room, messages] : room_sync_messages) { - for(auto &message : messages) { + for(auto &[room, sync_data] : room_sync_data) { + for(auto &message : sync_data.messages) { if(message->mentions_me) { room->has_unread_mention = true; // TODO: What if the message or username begins with "-"? also make the notification image be the avatar of the user @@ -3005,8 +3020,8 @@ namespace QuickMedia { } } - for(auto &[room, messages] : room_sync_messages) { - if(messages.empty()) + for(auto &[room, sync_data] : room_sync_data) { + if(sync_data.messages.empty()) continue; std::shared_ptr me = matrix->get_me(room); @@ -3020,7 +3035,7 @@ 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. // TODO: Binary search? Message *last_unread_message = nullptr; - for(auto &message : messages) { + for(auto &message : sync_data.messages) { if(message->related_event_type != RelatedEventType::EDIT && message->related_event_type != RelatedEventType::REDACTION && message->timestamp > read_marker_message_timestamp) last_unread_message = message.get(); } @@ -3060,7 +3075,7 @@ namespace QuickMedia { } } else if(is_first_sync) { Message *last_unread_message = nullptr; - for(auto it = messages.rbegin(), end = messages.rend(); it != end; ++it) { + for(auto it = sync_data.messages.rbegin(), end = sync_data.messages.rend(); it != end; ++it) { if((*it)->related_event_type != RelatedEventType::EDIT && (*it)->related_event_type != RelatedEventType::REDACTION) { last_unread_message = (*it).get(); break; @@ -3170,6 +3185,19 @@ namespace QuickMedia { } }; + auto process_new_pinned_events = [&tabs](const std::vector &pinned_events) { + // TODO: Add message to rooms messages when there are new pinned events + for(const std::string &event : pinned_events) { + auto body = BodyItem::create(""); + body->set_description("Loading message..."); + PinnedEventData *event_data = new PinnedEventData(); + event_data->event_id = event; + event_data->status = PinnedEventStatus::NONE; + body->userdata = event_data; + tabs[PINNED_TAB_INDEX].body->items.push_back(std::move(body)); + } + }; + 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) { @@ -3179,7 +3207,7 @@ namespace QuickMedia { room_search_bar.onTextSubmitCallback = [this, &tabs, &selected_tab, ¤t_room, &room_name_text, - &modify_related_messages_in_current_room, &room_avatar_thumbnail_data, + &modify_related_messages_in_current_room, &process_new_pinned_events, &room_avatar_thumbnail_data, &read_marker_timeout_ms, &redraw, &room_search_bar] (const std::string&) { @@ -3194,15 +3222,20 @@ namespace QuickMedia { 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())); - modify_related_messages_in_current_room(new_messages); - tabs[MESSAGES_TAB_INDEX].body->select_last_item(); - } else { - std::string err_msg = "Failed to get messages in room: " + current_room->id; - show_notification("QuickMedia", err_msg, Urgency::CRITICAL); + for(auto &body_item : tabs[PINNED_TAB_INDEX].body->items) { + delete((PinnedEventData*)body_item->userdata); } + tabs[PINNED_TAB_INDEX].body->clear_items(); + + Messages all_messages; + matrix->get_all_synced_room_messages(current_room, all_messages); + tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps(messages_to_body_items(all_messages, matrix->get_me(current_room).get())); + modify_related_messages_in_current_room(all_messages); + tabs[MESSAGES_TAB_INDEX].body->select_last_item(); + + std::vector pinned_events; + matrix->get_all_pinned_events(current_room, pinned_events); + process_new_pinned_events(pinned_events); room_name_text.setString(static_cast(current_room->userdata)->get_title()); room_avatar_thumbnail_data = std::make_shared(); @@ -3217,11 +3250,11 @@ namespace QuickMedia { chat_input.draw_background = false; chat_input.set_editable(false); - chat_input.on_submit_callback = [this, &chat_input, &tabs, &selected_tab, ¤t_room, &new_page, &chat_state, ¤tly_operating_on_item](const std::string &text) mutable { + chat_input.on_submit_callback = [this, &chat_input, &selected_tab, ¤t_room, &new_page, &chat_state, ¤tly_operating_on_item](const std::string &text) mutable { if(!current_room) return false; - if(tabs[selected_tab].type == ChatTabType::MESSAGES) { + if(selected_tab == MESSAGES_TAB_INDEX) { if(text.empty()) return false; @@ -3282,7 +3315,7 @@ namespace QuickMedia { struct SyncFutureResult { Rooms rooms; - RoomSyncMessages room_sync_messages; + RoomSyncData room_sync_data; }; std::future sync_future; @@ -3294,18 +3327,52 @@ namespace QuickMedia { bool fetching_previous_messages_running = false; RoomData *previous_messages_future_room = nullptr; - std::future> fetch_reply_message_future; - bool fetching_reply_message_running = false; - RoomData *fetch_reply_future_room = nullptr; - BodyItem *fetch_reply_body_item = nullptr; + //const int num_fetch_message_threads = 4; + std::future> fetch_message_future; + bool fetching_message_running = false; + RoomData *fetch_future_room = nullptr; + BodyItem *fetch_body_item = nullptr; + int fetch_message_tab = -1; // 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) + tabs[PINNED_TAB_INDEX].body->body_item_render_callback = [this, ¤t_room, &fetch_message_future, &tabs, &find_body_item_by_event_id, &fetching_message_running, &fetch_future_room, &fetch_body_item, &fetch_message_tab](BodyItem *body_item) { + if(fetching_message_running || !current_room) + return; + + PinnedEventData *event_data = static_cast(body_item->userdata); + if(!event_data || event_data->status != PinnedEventStatus::NONE) + return; + + // Check if we already have the referenced message as a body item in the messages list, 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(), event_data->event_id); + if(related_body_item) { + *body_item = *related_body_item; + event_data->status = PinnedEventStatus::FINISHED_LOADING; + body_item->userdata = event_data; + return; + } + + fetching_message_running = true; + std::string message_event_id = event_data->event_id; + fetch_future_room = current_room; + fetch_body_item = body_item; + body_item->embedded_item_status = EmbeddedItemStatus::LOADING; + fetch_message_tab = PINNED_TAB_INDEX; + // TODO: Check if the message is already cached before calling async? is this needed? is async creation expensive? + fetch_message_future = std::async(std::launch::async, [this, &fetch_future_room, message_event_id]() { + return matrix->get_message_by_id(fetch_future_room, message_event_id); + }); + }; + + // 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_message_future, &tabs, &find_body_item_by_event_id, &fetching_message_running, &fetch_future_room, &fetch_body_item, &fetch_message_tab](BodyItem *body_item) { + if(fetching_message_running || !current_room) return; Message *message = static_cast(body_item->userdata); + assert(message); if(message->related_event_id.empty() || body_item->embedded_item_status != EmbeddedItemStatus::NONE) return; @@ -3317,14 +3384,15 @@ namespace QuickMedia { return; } - fetching_reply_message_running = true; + fetching_message_running = true; std::string message_event_id = message->related_event_id; - fetch_reply_future_room = current_room; - fetch_reply_body_item = body_item; + fetch_future_room = current_room; + fetch_body_item = body_item; body_item->embedded_item_status = EmbeddedItemStatus::LOADING; + fetch_message_tab = MESSAGES_TAB_INDEX; // 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, message_event_id); + fetch_message_future = std::async(std::launch::async, [this, &fetch_future_room, message_event_id]() { + return matrix->get_message_by_id(fetch_future_room, message_event_id); }); }; @@ -3403,10 +3471,10 @@ namespace QuickMedia { } }; - auto add_new_messages_to_current_room = [this, &tabs, &selected_tab, ¤t_room, &is_window_focused](Messages &messages) { + auto add_new_messages_to_current_room = [this, &tabs, &selected_tab, ¤t_room](Messages &messages) { int num_items = tabs[MESSAGES_TAB_INDEX].body->items.size(); 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) + if(tabs[MESSAGES_TAB_INDEX].body->is_selected_item_last_visible_item() && selected_tab == MESSAGES_TAB_INDEX) scroll_to_end = true; BodyItem *selected_item = tabs[MESSAGES_TAB_INDEX].body->get_selected(); @@ -3456,7 +3524,7 @@ namespace QuickMedia { 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(selected_tab == ROOMS_TAB_INDEX) { if(event.type == sf::Event::TextEntered) room_search_bar.onTextEntered(event.text.unicode); room_search_bar.on_event(event); @@ -3487,7 +3555,7 @@ namespace QuickMedia { hit_top = false; break; } - if(hit_top && !fetching_previous_messages_running && tabs[selected_tab].type == ChatTabType::MESSAGES && current_room) { + if(hit_top && !fetching_previous_messages_running && selected_tab == MESSAGES_TAB_INDEX && current_room) { gradient_inc = 0; fetching_previous_messages_running = true; previous_messages_future_room = current_room; @@ -3526,7 +3594,7 @@ namespace QuickMedia { } } - if(tabs[selected_tab].type == ChatTabType::MESSAGES && event.key.code == sf::Keyboard::Enter) { + if(selected_tab == MESSAGES_TAB_INDEX && event.key.code == sf::Keyboard::Enter) { BodyItem *selected = tabs[selected_tab].body->get_selected(); if(selected) { MessageType message_type = static_cast(selected->userdata)->type; @@ -3603,7 +3671,7 @@ namespace QuickMedia { continue; launch_url(selected_item->get_title()); } - } else if(event.type == sf::Event::KeyReleased && chat_state == ChatState::NAVIGATING && tabs[selected_tab].type == ChatTabType::MESSAGES && current_room) { + } else if(event.type == sf::Event::KeyReleased && chat_state == ChatState::NAVIGATING && selected_tab == MESSAGES_TAB_INDEX && current_room) { if(event.key.code == sf::Keyboard::U) { new_page = PageType::FILE_MANAGER; chat_input.set_editable(false); @@ -3676,7 +3744,7 @@ namespace QuickMedia { } } - if((chat_state == ChatState::TYPING_MESSAGE || chat_state == ChatState::REPLYING || chat_state == ChatState::EDITING) && tabs[selected_tab].type == ChatTabType::MESSAGES) { + if((chat_state == ChatState::TYPING_MESSAGE || chat_state == ChatState::REPLYING || chat_state == ChatState::EDITING) && selected_tab == MESSAGES_TAB_INDEX) { if(event.type == sf::Event::TextEntered) { //chat_input.onTextEntered(event.text.unicode); // TODO: Also show typing event when ctrl+v pasting? @@ -3825,6 +3893,8 @@ namespace QuickMedia { room_name_padding_y = room_search_bar.getBottomWithoutShadow(); tab_vertical_offset = 0.0f; padding_bottom = 10.0f; + } else if(selected_tab == PINNED_TAB_INDEX) { + padding_bottom = 10.0f; } tab_shade_height = tab_spacer_height + std::floor(tab_vertical_offset) + tab_height + room_name_padding_y + padding_bottom; @@ -3861,7 +3931,7 @@ namespace QuickMedia { sync_timer.restart(); sync_future = std::async(std::launch::async, [this]() { SyncFutureResult result; - if(matrix->sync(result.room_sync_messages) == PluginResult::OK) { + if(matrix->sync(result.room_sync_data) == PluginResult::OK) { fprintf(stderr, "Synced matrix\n"); matrix->get_room_join_updates(result.rooms); } else { @@ -3877,13 +3947,14 @@ namespace QuickMedia { add_new_rooms(sync_result.rooms); - auto room_messages_it = sync_result.room_sync_messages.find(current_room); - if(room_messages_it != sync_result.room_sync_messages.end()) { - add_new_messages_to_current_room(room_messages_it->second); - modify_related_messages_in_current_room(room_messages_it->second); + auto room_messages_it = sync_result.room_sync_data.find(current_room); + if(room_messages_it != sync_result.room_sync_data.end()) { + add_new_messages_to_current_room(room_messages_it->second.messages); + modify_related_messages_in_current_room(room_messages_it->second.messages); + process_new_pinned_events(room_messages_it->second.pinned_events); } - process_new_room_messages(sync_result.room_sync_messages, !synced); + process_new_room_messages(sync_result.room_sync_data, !synced); sync_running = false; synced = true; } @@ -3915,19 +3986,31 @@ 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)"); + if(fetching_message_running && fetch_message_future.valid() && fetch_message_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { + std::shared_ptr message = fetch_message_future.get(); + fprintf(stderr, "Finished fetching message: %s\n", message ? 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(), matrix->get_me(current_room).get()); - fetch_reply_body_item->embedded_item_status = EmbeddedItemStatus::FINISHED_LOADING; - } else { - fetch_reply_body_item->embedded_item_status = EmbeddedItemStatus::FAILED_TO_LOAD; + if(fetch_future_room == current_room) { + if(fetch_message_tab == PINNED_TAB_INDEX) { + PinnedEventData *event_data = static_cast(fetch_body_item->userdata); + if(message) { + *fetch_body_item = *message_to_body_item(message.get(), matrix->get_me(current_room).get()); + event_data->status = PinnedEventStatus::FINISHED_LOADING; + fetch_body_item->userdata = event_data; + } else { + event_data->status = PinnedEventStatus::FAILED_TO_LOAD; + } + } else if(fetch_message_tab == MESSAGES_TAB_INDEX) { + if(message) { + fetch_body_item->embedded_item = message_to_body_item(message.get(), matrix->get_me(current_room).get()); + fetch_body_item->embedded_item_status = EmbeddedItemStatus::FINISHED_LOADING; + } else { + fetch_body_item->embedded_item_status = EmbeddedItemStatus::FAILED_TO_LOAD; + } } } - fetching_reply_message_running = false; + fetching_message_running = false; + fetch_message_tab = -1; } //chat_input.update(); @@ -3946,7 +4029,7 @@ namespace QuickMedia { tab_shade.setSize(sf::Vector2f(window_size.x, tab_shade_height)); window.draw(tab_shade); - if(tabs[selected_tab].type == ChatTabType::MESSAGES) { + if(selected_tab == MESSAGES_TAB_INDEX) { float room_name_text_offset_x = 0.0f; if(room_avatar_sprite.getTexture() && room_avatar_sprite.getTexture()->getNativeHandle() != 0) { auto room_avatar_texture_size = room_avatar_sprite.getTexture()->getSize(); @@ -3958,7 +4041,7 @@ 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) { + } else if(selected_tab == ROOMS_TAB_INDEX) { room_search_bar.draw(window, false); } @@ -3987,7 +4070,7 @@ namespace QuickMedia { } // TODO: Have one for each room. Also add bottom one? for fetching new messages (currently not implemented, is it needed?) - if(fetching_previous_messages_running && tabs[selected_tab].type == ChatTabType::MESSAGES) { + if(fetching_previous_messages_running && selected_tab == MESSAGES_TAB_INDEX) { double progress = 0.5 + std::sin(std::fmod(gradient_inc, 360.0) * 0.017453292519943295 - 1.5707963267948966*0.5) * 0.5; gradient_inc += (frame_time_ms * 0.5); sf::Color top_color = interpolate_colors(back_color, sf::Color(175, 180, 188), progress); @@ -4024,7 +4107,7 @@ namespace QuickMedia { tabs[MESSAGES_TAB_INDEX].body->draw_item(window, currently_operating_on_item.get(), body_item_pos, body_item_size); } - if(tabs[selected_tab].type == ChatTabType::MESSAGES && current_room && current_room->userdata && !current_room->last_message_read) { + if(selected_tab == MESSAGES_TAB_INDEX && current_room && current_room->userdata && !current_room->last_message_read) { if(tabs[selected_tab].body->is_last_item_fully_visible()) { BodyItem *current_room_body_item = static_cast(current_room->userdata); std::string room_desc = current_room_body_item->get_description(); @@ -4050,7 +4133,7 @@ namespace QuickMedia { window.draw(loading_text); } - if(tabs[selected_tab].type == ChatTabType::MESSAGES && current_room) { + if(selected_tab == MESSAGES_TAB_INDEX && current_room) { BodyItem *last_visible_item = tabs[selected_tab].body->get_last_fully_visible_item(); if(is_window_focused && chat_state != ChatState::URL_SELECTION && current_room && last_visible_item && !setting_read_marker && read_marker_timer.getElapsedTime().asMilliseconds() >= read_marker_timeout_ms) { Message *message = (Message*)last_visible_item->userdata; -- cgit v1.2.3