diff options
-rw-r--r-- | TODO | 9 | ||||
-rw-r--r-- | include/Body.hpp | 2 | ||||
-rw-r--r-- | include/MessageQueue.hpp | 9 | ||||
-rw-r--r-- | plugins/Matrix.hpp | 11 | ||||
-rw-r--r-- | src/Body.cpp | 19 | ||||
-rw-r--r-- | src/QuickMedia.cpp | 282 | ||||
-rw-r--r-- | src/Text.cpp | 6 | ||||
-rw-r--r-- | src/plugins/Matrix.cpp | 37 |
8 files changed, 264 insertions, 111 deletions
@@ -106,7 +106,7 @@ Room list in matrix ignores edited messages, which it should for unread messages Fetch replies/pinned message using multiple threads. Show in room tags list when there is a message in any of the rooms in the tag. Cancel video download when pressing escape or closing window (important in matrix). -Support webp. +Support webp (or it seems to already be supported?). Show images while they download by showing them as scanlines starting from the top. Needed for slow websites such as 4chan. Use curl parallel download instead of downloading with multiple threads. Handle matrix groups? (which also contains join, invite, leave...). @@ -118,7 +118,6 @@ Make /logout work everywhere, not only in room message input. Add a notifications tab to show messages that mention us in all rooms (and then press enter to go to that message in that room), also add a unread/mentioned rooms list tab to only show rooms with unread messages or mentions. Disable message input in matrix when muted. Preview rooms? -Instantly show messages posted by me until we receive our message from the server (use post response which includes event id). Handle matrix token being invalidated while running. Update upload limit if its updated on the server (can it be updated while the server is running?). Editing a reply removes reply formatting (both in body and formatted_body). Element also does this when you edit a reply twice. This breaks element mobile that is unable to display replied-to messages without correct formatting (doesn't fetch the replied-to message). @@ -127,4 +126,8 @@ Show a marker when a room uses encryption. Remove replied-to message text in room preview. That shows ignored users text and we want to see the reply message instead anyways. Update room name/avatar with new data in /sync. Read marker is incorrect if the last message is an edit/redact, because they are hidden and replaces other body items instead. -Scroll tabs if there are more than 3 tab items and show arrow on left/right side when there are more items to see.
\ No newline at end of file +Scroll tabs if there are more than 3 tab items and show arrow on left/right side when there are more items to see. +Make a shader for Text for changing color instead of updating the text geometry. Or loop vertices and set their color to the new color without updating the text geometry. +Automatically retry sending messages that fails to send (after timeout). +Also make message deletion provisional (make it gray while its deleting the message). +Continue matrix requests when switching room, instead of resetting them when going from chat page to room list page (such as post message request).
\ No newline at end of file diff --git a/include/Body.hpp b/include/Body.hpp index f4742bc..236a02d 100644 --- a/include/Body.hpp +++ b/include/Body.hpp @@ -207,6 +207,8 @@ namespace QuickMedia { float get_page_scroll() const { return page_scroll; } bool is_last_item_fully_visible() const { return last_item_fully_visible; } + bool is_body_full_with_items() const { return items_cut_off; } + sf::Text progress_text; sf::Text replies_text; sf::Text embedded_item_load_text; diff --git a/include/MessageQueue.hpp b/include/MessageQueue.hpp index 4f1e036..7c34d51 100644 --- a/include/MessageQueue.hpp +++ b/include/MessageQueue.hpp @@ -31,6 +31,15 @@ namespace QuickMedia { return data; } + std::optional<T> pop_if_available() { + std::unique_lock<std::mutex> lock(mutex); + if(data_queue.empty()) + return std::nullopt; + T data = std::move(data_queue.front()); + data_queue.pop_front(); + return data; + } + void close() { std::unique_lock<std::mutex> lock(mutex); running = false; diff --git a/plugins/Matrix.hpp b/plugins/Matrix.hpp index 30cfa92..7390062 100644 --- a/plugins/Matrix.hpp +++ b/plugins/Matrix.hpp @@ -61,7 +61,7 @@ namespace QuickMedia { std::string thumbnail_url; std::string related_event_id; sf::Vector2i thumbnail_size; // Set to {0, 0} if not specified - RelatedEventType related_event_type; + RelatedEventType related_event_type = RelatedEventType::NONE; bool mentions_me = false; time_t timestamp = 0; // In milliseconds MessageType type; @@ -179,7 +179,6 @@ namespace QuickMedia { struct SyncData { Messages messages; std::optional<std::vector<std::string>> pinned_events; - std::optional<std::vector<std::string>> tags; }; using Rooms = std::vector<RoomData*>; @@ -437,13 +436,13 @@ namespace QuickMedia { // |url| should only be set when uploading media. // TODO: Make api better. - PluginResult post_message(RoomData *room, const std::string &body, const std::optional<UploadInfo> &file_info, const std::optional<UploadInfo> &thumbnail_info, const std::string &msgtype = ""); + PluginResult post_message(RoomData *room, const std::string &body, std::string &event_id_response, const std::optional<UploadInfo> &file_info, const std::optional<UploadInfo> &thumbnail_info, const std::string &msgtype = ""); // |relates_to| is from |BodyItem.userdata| and is of type |Message*| - PluginResult post_reply(RoomData *room, const std::string &body, void *relates_to); + PluginResult post_reply(RoomData *room, const std::string &body, void *relates_to, std::string &event_id_response); // |relates_to| is from |BodyItem.userdata| and is of type |Message*| - PluginResult post_edit(RoomData *room, const std::string &body, void *relates_to); + PluginResult post_edit(RoomData *room, const std::string &body, void *relates_to, std::string &event_id_response); - PluginResult post_file(RoomData *room, const std::string &filepath, std::string &err_msg); + PluginResult post_file(RoomData *room, const std::string &filepath, std::string &event_id_response, std::string &err_msg); PluginResult login(const std::string &username, const std::string &password, const std::string &homeserver, std::string &err_msg); PluginResult logout(); diff --git a/src/Body.cpp b/src/Body.cpp index 2cf6f78..b456841 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -260,7 +260,7 @@ namespace QuickMedia { // TODO: Optimize by resizing |items| before insert void Body::insert_items_by_timestamps(BodyItems new_items) { for(auto &new_item : new_items) { - insert_item_by_timestamp(std::move(new_item)); + insert_item_by_timestamp(new_item); } } @@ -435,8 +435,8 @@ namespace QuickMedia { pos.y += page_scroll; - last_item_fully_visible = true; - items_cut_off = false; + bool last_item_fully_visible_set = false; + bool items_cut_off_set = false; sf::Vector2u window_size = window.getSize(); @@ -453,8 +453,10 @@ namespace QuickMedia { float item_height = get_item_height(item.get(), size.x); prev_pos.y -= (item_height + spacing_y); - if(prev_pos.y < start_y) + if(prev_pos.y < start_y) { items_cut_off = true; + items_cut_off_set = true; + } if(prev_pos.y + item_height + spacing_y <= start_y) break; @@ -480,6 +482,8 @@ namespace QuickMedia { if(after_pos.y - start_y >= size.y) { last_item_fully_visible = false; items_cut_off = true; + last_item_fully_visible_set = true; + items_cut_off_set = true; break; } @@ -497,6 +501,8 @@ namespace QuickMedia { if(after_pos.y - start_y > size.y) { last_item_fully_visible = false; items_cut_off = true; + last_item_fully_visible_set = true; + items_cut_off_set = true; } else { last_fully_visible_item = i; } @@ -507,6 +513,11 @@ namespace QuickMedia { offset_to_bottom = size.y - (after_pos.y - start_y); + if(!last_item_fully_visible_set) + last_item_fully_visible = true; + if(!items_cut_off_set) + items_cut_off = false; + for(auto it = item_thumbnail_textures.begin(); it != item_thumbnail_textures.end();) { if(!it->second->referenced) it = item_thumbnail_textures.erase(it); diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index c01ce59..5c97c2d 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -3199,41 +3199,50 @@ namespace QuickMedia { bool redraw = true; // 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> { + auto find_body_item_by_event_id = [](std::shared_ptr<BodyItem> *body_items, size_t num_body_items, const std::string &event_id, size_t *index_result = nullptr) -> 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) + if(static_cast<Message*>(body_item->userdata)->event_id == event_id) { + if(index_result) + *index_result = i; return body_item; + } } return nullptr; }; // TODO: What if these never end up referencing events? clean up automatically after a while? - std::unordered_map<RoomData*, Messages> unreferenced_event_by_room; + Messages unreferenced_events; auto set_body_as_deleted = [¤t_room](Message *message, BodyItem *body_item) { body_item->embedded_item = nullptr; body_item->embedded_item_status = FetchStatus::NONE; + message->type = MessageType::REDACTION; + message->related_event_id.clear(); + message->related_event_type = RelatedEventType::NONE; body_item->thumbnail_url = current_room->get_user_avatar_url(message->user); body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE; + body_item->set_description("Message deleted"); 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<BodyItem> *body_items, size_t num_body_items) { - auto &unreferenced_events = unreferenced_event_by_room[current_room]; + auto resolve_unreferenced_events_with_body_items = [&set_body_as_deleted, &unreferenced_events, &find_body_item_by_event_id](std::shared_ptr<BodyItem> *body_items, size_t num_body_items) { 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_get_body_remove_formatting(message.get())); // TODO: Append the new message to the body item so the body item should have a list of edit events //body_item->userdata = message.get(); - if(message->related_event_type == RelatedEventType::REDACTION) + if(message->related_event_type == RelatedEventType::REDACTION) { set_body_as_deleted(message.get(), body_item.get()); + } else { + body_item->set_description(message_get_body_remove_formatting(message.get())); + body_item->set_description_color(sf::Color::White); + } it = unreferenced_events.erase(it); } else { ++it; @@ -3245,22 +3254,24 @@ namespace QuickMedia { }; // 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 modify_related_messages_in_current_room = [&set_body_as_deleted, &unreferenced_events, &find_body_item_by_event_id, &tabs](Messages &messages) { if(messages.empty()) return; - 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_get_body_remove_formatting(message.get())); // TODO: Append the new message to the body item so the body item should have a list of edit events //body_item->userdata = message.get(); - if(message->related_event_type == RelatedEventType::REDACTION) + if(message->related_event_type == RelatedEventType::REDACTION) { set_body_as_deleted(message.get(), body_item.get()); + } else { + body_item->set_description(message_get_body_remove_formatting(message.get())); + body_item->set_description_color(sf::Color::White); + } } else { unreferenced_events.push_back(message); } @@ -3350,18 +3361,64 @@ namespace QuickMedia { chat_input.draw_background = false; chat_input.set_editable(false); - MessageQueue<std::function<void()>> post_task_queue; - auto post_thread_handler = [&post_task_queue]() { + struct ProvisionalMessage { + std::shared_ptr<BodyItem> body_item; + std::shared_ptr<Message> message; + std::string event_id; + }; + + std::vector<ProvisionalMessage> unresolved_provisional_messages; // |event_id| is always empty in this. Use |message->event_id| instead + std::optional<ProvisionalMessage> provisional_message; + MessageQueue<ProvisionalMessage> provisional_message_queue; + MessageQueue<std::function<ProvisionalMessage()>> post_task_queue; + auto post_thread_handler = [&provisional_message_queue, &post_task_queue]() { while(true) { - std::optional<std::function<void()>> post_task_opt = post_task_queue.pop_wait(); + std::optional<std::function<ProvisionalMessage()>> post_task_opt = post_task_queue.pop_wait(); if(!post_task_opt) break; - post_task_opt.value()(); + provisional_message_queue.push(post_task_opt.value()()); } }; std::thread post_thread(post_thread_handler); - chat_input.on_submit_callback = [this, &chat_input, &selected_tab, ¤t_room, &new_page, &chat_state, ¤tly_operating_on_item, &post_task_queue](std::string text) mutable { + auto resolve_provisional_messages = [&unresolved_provisional_messages](Messages &messages) { + if(messages.empty() || unresolved_provisional_messages.empty()) + return; + + for(auto it = unresolved_provisional_messages.begin(); it != unresolved_provisional_messages.end();) { + auto find_it = std::find_if(messages.cbegin(), messages.cend(), [&it](const std::shared_ptr<Message> &message) { + return message->event_id == it->message->event_id; + }); + if(find_it != messages.end()) { + it->body_item->set_description_color(sf::Color::White); + it->body_item->userdata = find_it->get(); // This is ok because the message is added to |all_messages| + it = unresolved_provisional_messages.erase(it); + messages.erase(find_it); + } else { + ++it; + } + } + }; + + auto upload_file = [this, &tabs, ¤t_room](const std::string &filepath) { + TaskResult post_file_result = run_task_with_loading_screen([this, ¤t_room, filepath]() { + std::string event_id_response; + std::string err_msg; + if(matrix->post_file(current_room, filepath, event_id_response, err_msg) == PluginResult::OK) { + return true; + } else { + show_notification("QuickMedia", "Failed to upload media to room, error: " + err_msg, Urgency::CRITICAL); + return false; + } + }); + + if(post_file_result == TaskResult::TRUE) { + if(tabs[MESSAGES_TAB_INDEX].body->is_last_item_fully_visible()) + tabs[MESSAGES_TAB_INDEX].body->select_last_item(); + } + }; + + chat_input.on_submit_callback = [this, &tabs, &me, &chat_input, &selected_tab, ¤t_room, &new_page, &chat_state, ¤tly_operating_on_item, &post_task_queue, &unreferenced_events, &find_body_item_by_event_id](std::string text) mutable { if(!current_room) return false; @@ -3400,34 +3457,82 @@ namespace QuickMedia { } } + auto message = std::make_shared<Message>(); + message->user = matrix->get_me(current_room); + message->body = text; + message->type = MessageType::TEXT; + message->timestamp = time(NULL) * 1000; + + const sf::Color provisional_message_color(171, 175, 180); + + 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) + scroll_to_end = true; + if(chat_state == ChatState::TYPING_MESSAGE) { - post_task_queue.push([this, ¤t_room, text, msgtype]() { - if(matrix->post_message(current_room, text, std::nullopt, std::nullopt, msgtype) != PluginResult::OK) + auto body_item = message_to_body_item(current_room, message.get(), current_room->get_user_avatar_url(me), me->user_id); + body_item->set_description_color(provisional_message_color); + tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps({body_item}); + post_task_queue.push([this, ¤t_room, text, msgtype, body_item, message]() { + ProvisionalMessage provisional_message; + provisional_message.body_item = body_item; + provisional_message.message = message; + if(matrix->post_message(current_room, text, provisional_message.event_id, std::nullopt, std::nullopt, msgtype) != PluginResult::OK) fprintf(stderr, "Failed to post matrix message\n"); + return provisional_message; }); chat_input.set_editable(false); chat_state = ChatState::NAVIGATING; + if(scroll_to_end) + tabs[MESSAGES_TAB_INDEX].body->select_last_item(); return true; } else if(chat_state == ChatState::REPLYING) { void *related_to_message = currently_operating_on_item->userdata; - post_task_queue.push([this, ¤t_room, text, related_to_message]() { - if(matrix->post_reply(current_room, text, related_to_message) != PluginResult::OK) + message->related_event_type = RelatedEventType::REPLY; + message->related_event_id = static_cast<Message*>(related_to_message)->event_id; + auto body_item = message_to_body_item(current_room, message.get(), current_room->get_user_avatar_url(me), me->user_id); + body_item->set_description_color(provisional_message_color); + tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps({body_item}); + post_task_queue.push([this, ¤t_room, text, related_to_message, body_item, message]() { + ProvisionalMessage provisional_message; + provisional_message.body_item = body_item; + provisional_message.message = message; + if(matrix->post_reply(current_room, text, related_to_message, provisional_message.event_id) != PluginResult::OK) fprintf(stderr, "Failed to post matrix reply\n"); + return provisional_message; }); chat_input.set_editable(false); chat_state = ChatState::NAVIGATING; currently_operating_on_item = nullptr; + if(scroll_to_end) + tabs[MESSAGES_TAB_INDEX].body->select_last_item(); return true; } else if(chat_state == ChatState::EDITING) { void *related_to_message = currently_operating_on_item->userdata; - post_task_queue.push([this, ¤t_room, text, related_to_message]() { - if(matrix->post_edit(current_room, text, related_to_message) != PluginResult::OK) - fprintf(stderr, "Failed to post matrix edit\n"); - }); - chat_input.set_editable(false); - chat_state = ChatState::NAVIGATING; - currently_operating_on_item = nullptr; - return true; + message->related_event_type = RelatedEventType::EDIT; + message->related_event_id = static_cast<Message*>(related_to_message)->event_id; + auto body_item = find_body_item_by_event_id(tabs[MESSAGES_TAB_INDEX].body->items.data(), tabs[MESSAGES_TAB_INDEX].body->items.size(), message->related_event_id); + if(body_item) { + body_item->set_description(text); + body_item->set_description_color(provisional_message_color); + unreferenced_events.push_back(message); + post_task_queue.push([this, ¤t_room, text, related_to_message, message]() { + ProvisionalMessage provisional_message; + provisional_message.message = message; + if(matrix->post_edit(current_room, text, related_to_message, provisional_message.event_id) != PluginResult::OK) + fprintf(stderr, "Failed to post matrix edit\n"); + return provisional_message; + }); + + chat_input.set_editable(false); + chat_state = ChatState::NAVIGATING; + currently_operating_on_item = nullptr; + return true; + } else { + show_notification("QuickMedia", "Failed to edit message. Message refers to a non-existing message", Urgency::CRITICAL); + return false; + } } } return false; @@ -3567,10 +3672,10 @@ namespace QuickMedia { bool fetched_enough_messages = false; bool initial_prev_messages_fetch = true; - auto fetch_more_previous_messages_if_needed = [this, &all_messages, ¤t_room, &fetched_enough_messages, &previous_messages_future, &initial_prev_messages_fetch]() { + auto fetch_more_previous_messages_if_needed = [this, &tabs, ¤t_room, &fetched_enough_messages, &previous_messages_future, &initial_prev_messages_fetch]() { if(!fetched_enough_messages && !previous_messages_future.ready()) { bool fetch_latest_messages = !matrix->is_initial_sync_finished() && initial_prev_messages_fetch; - if(all_messages.size() < 10) { + if(!tabs[MESSAGES_TAB_INDEX].body->is_body_full_with_items()) { previous_messages_future = [this, ¤t_room, fetch_latest_messages]() { Messages messages; if(matrix->get_previous_room_messages(current_room, messages, fetch_latest_messages) != PluginResult::OK) @@ -3794,7 +3899,7 @@ namespace QuickMedia { } }; - auto cleanup_tasks = [&set_read_marker_future, &fetch_message_future, &typing_state_queue, &typing_state_thread, &post_task_queue, &post_thread, &tabs]() { + auto cleanup_tasks = [&set_read_marker_future, &fetch_message_future, &typing_state_queue, &typing_state_thread, &post_task_queue, &provisional_message_queue, &post_thread, &tabs]() { set_read_marker_future.cancel(); fetch_message_future.cancel(); typing_state_queue.close(); @@ -3807,6 +3912,7 @@ namespace QuickMedia { program_kill_in_thread(post_thread.get_id()); post_thread.join(); } + provisional_message_queue.clear(); //unreferenced_event_by_room.clear(); @@ -3940,20 +4046,7 @@ namespace QuickMedia { if(event.key.control && event.key.code == sf::Keyboard::V) { frame_skip_text_entry = true; // TODO: Upload multiple files. - std::string selected_file = sf::Clipboard::getString(); - TaskResult post_file_result = run_task_with_loading_screen([this, ¤t_room, selected_file]() { - std::string err_msg; - if(matrix->post_file(current_room, selected_file, err_msg) == PluginResult::OK) { - return true; - } else { - show_notification("QuickMedia", "Failed to upload media to room, error: " + err_msg, Urgency::CRITICAL); - return false; - } - }); - if(post_file_result == TaskResult::TRUE) { - if(tabs[MESSAGES_TAB_INDEX].body->is_last_item_fully_visible()) - tabs[MESSAGES_TAB_INDEX].body->select_last_item(); - } + upload_file(sf::Clipboard::getString()); } if(event.key.code == sf::Keyboard::R) { @@ -3961,10 +4054,15 @@ namespace QuickMedia { std::shared_ptr<BodyItem> selected = tabs[selected_tab].body->get_selected_shared(); if(selected) { if(!is_state_message_type(static_cast<Message*>(selected->userdata))) { - chat_state = ChatState::REPLYING; - currently_operating_on_item = selected; - chat_input.set_editable(true); - replying_to_text.setString("Replying to:"); + if(static_cast<Message*>(selected->userdata)->event_id.empty()) { + // TODO: Show inline notification + show_notification("QuickMedia", "You can't reply to a message that hasn't been sent yet"); + } else { + chat_state = ChatState::REPLYING; + currently_operating_on_item = selected; + chat_input.set_editable(true); + replying_to_text.setString("Replying to:"); + } } } else { // TODO: Show inline notification @@ -3977,9 +4075,12 @@ namespace QuickMedia { std::shared_ptr<BodyItem> selected = tabs[selected_tab].body->get_selected_shared(); if(selected) { if(!is_state_message_type(static_cast<Message*>(selected->userdata))) { - if(!selected->url.empty()) { // cant edit messages that are image/video posts + if(static_cast<Message*>(selected->userdata)->event_id.empty()) { + // TODO: Show inline notification + show_notification("QuickMedia", "You can't edit a message that hasn't been sent yet"); + } else if(!selected->url.empty()) { // cant edit messages that are image/video posts // TODO: Show inline notification - show_notification("QuickMedia", "You can only edit messages with no file attached to it"); + show_notification("QuickMedia", "You can't edit messages with files attached to them"); } else if(!matrix->was_message_posted_by_me(selected->userdata)) { // TODO: Show inline notification show_notification("QuickMedia", "You can't edit a message that was posted by somebody else"); @@ -4003,14 +4104,22 @@ namespace QuickMedia { BodyItem *selected = tabs[selected_tab].body->get_selected(); if(selected) { if(!is_state_message_type(static_cast<Message*>(selected->userdata))) { - void *selected_message = selected->userdata; - post_task_queue.push([this, ¤t_room, selected_message]() { - std::string err_msg; - if(matrix->delete_message(current_room, selected_message, err_msg) != PluginResult::OK) { - // TODO: Show inline notification - fprintf(stderr, "Failed to delete message, reason: %s\n", err_msg.c_str()); - } - }); + if(static_cast<Message*>(selected->userdata)->event_id.empty()) { + // TODO: Show inline notification + show_notification("QuickMedia", "You can't delete a message that hasn't been sent yet"); + } else { + set_body_as_deleted(static_cast<Message*>(selected->userdata), selected); + void *selected_message = selected->userdata; + post_task_queue.push([this, ¤t_room, selected_message]() { + ProvisionalMessage provisional_message; + std::string err_msg; + if(matrix->delete_message(current_room, selected_message, err_msg) != PluginResult::OK) { + // TODO: Show inline notification + fprintf(stderr, "Failed to delete message, reason: %s\n", err_msg.c_str()); + } + return provisional_message; + }); + } } } else { // TODO: Show inline notification @@ -4106,19 +4215,7 @@ namespace QuickMedia { fprintf(stderr, "No files selected!\n"); } else { // TODO: Upload multiple files. - TaskResult post_file_result = run_task_with_loading_screen([this, ¤t_room]() { - std::string err_msg; - if(matrix->post_file(current_room, selected_files[0], err_msg) == PluginResult::OK) { - return true; - } else { - show_notification("QuickMedia", "Failed to upload media to room, error: " + err_msg, Urgency::CRITICAL); - return false; - } - }); - if(post_file_result == TaskResult::TRUE) { - if(tabs[MESSAGES_TAB_INDEX].body->is_last_item_fully_visible()) - tabs[MESSAGES_TAB_INDEX].body->select_last_item(); - } + upload_file(selected_files[0]); } redraw = true; } @@ -4128,7 +4225,7 @@ namespace QuickMedia { previous_messages_future.cancel(); cleanup_tasks(); tabs.clear(); - unreferenced_event_by_room.clear(); + unreferenced_events.clear(); all_messages.clear(); new_page = PageType::CHAT; matrix->stop_sync(); @@ -4223,10 +4320,25 @@ namespace QuickMedia { logo_sprite.setPosition(logo_padding_x, std::floor(window_size.y - chat_input_shade.getSize().y * 0.5f - logo_size.y * 0.5f)); } + while((provisional_message = provisional_message_queue.pop_if_available()) != std::nullopt) { + if(!provisional_message->body_item || !provisional_message->message) + continue; + + if(!provisional_message->event_id.empty()) { + provisional_message->message->event_id = std::move(provisional_message->event_id); + unresolved_provisional_messages.push_back(provisional_message.value()); + } else if(provisional_message->body_item) { + provisional_message->body_item->set_description("Failed to send: " + provisional_message->body_item->get_description()); + provisional_message->body_item->set_description_color(sf::Color::Red); + } + } + sync_data.messages.clear(); + sync_data.pinned_events = std::nullopt; matrix->get_room_sync_data(current_room, sync_data); if(!sync_data.messages.empty()) { all_messages.insert(all_messages.end(), sync_data.messages.begin(), sync_data.messages.end()); + resolve_provisional_messages(sync_data.messages); filter_existing_messages(sync_data.messages); } add_new_messages_to_current_room(sync_data.messages); @@ -4242,7 +4354,7 @@ namespace QuickMedia { if(previous_messages_future.ready()) { Messages new_messages = previous_messages_future.get(); all_messages.insert(all_messages.end(), new_messages.begin(), new_messages.end()); - if(new_messages.empty() || all_messages.size() >= 10) + if(new_messages.empty()) fetched_enough_messages = true; filter_existing_messages(new_messages); fprintf(stderr, "Finished fetching older messages, num new messages: %zu\n", new_messages.size()); @@ -4254,7 +4366,7 @@ namespace QuickMedia { BodyItem *selected_item = tabs[MESSAGES_TAB_INDEX].body->get_selected(); BodyItems new_body_items = messages_to_body_items(current_room, new_messages, current_room->get_user_display_name(me), me->user_id); size_t num_new_body_items = new_body_items.size(); - tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps(std::move(new_body_items)); + tabs[MESSAGES_TAB_INDEX].body->prepend_items(std::move(new_body_items)); // TODO: Insert by timestamp? then make sure num_new_body_items matches the first items blabla if(selected_item) { int selected_item_index = tabs[MESSAGES_TAB_INDEX].body->get_index_by_body_item(selected_item); if(selected_item_index != -1) @@ -4430,7 +4542,7 @@ namespace QuickMedia { 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; // TODO: What if two messages have the same timestamp? - if(message->timestamp > current_room->last_read_message_timestamp) { + if(message && !message->event_id.empty() && message->timestamp > current_room->last_read_message_timestamp) { //read_marker_timeout_ms = read_marker_timeout_ms_default; current_room->last_read_message_timestamp = message->timestamp; // TODO: What if the message is no longer valid? @@ -4464,7 +4576,6 @@ namespace QuickMedia { if(matrix_chat_page->should_clear_data) { matrix_chat_page->should_clear_data = false; - cleanup_tasks(); std::string err_msg; while(!matrix->is_initial_sync_finished()) { @@ -4478,13 +4589,22 @@ namespace QuickMedia { current_room = matrix->get_room_by_id(current_room->id); if(current_room) { + all_messages.clear(); + tabs[MESSAGES_TAB_INDEX].body->clear_items(); + + matrix->get_all_synced_room_messages(current_room, all_messages); + for(auto &message : all_messages) { + fetched_messages_set.insert(message->event_id); + } + auto me = matrix->get_me(current_room); + resolve_provisional_messages(all_messages); + add_new_messages_to_current_room(all_messages); + modify_related_messages_in_current_room(all_messages); + std::vector<std::string> pinned_events; matrix->get_all_pinned_events(current_room, pinned_events); process_pinned_events(std::move(pinned_events)); - typing_state_queue.restart(); - typing_state_thread = std::thread(typing_state_handler); - post_task_queue.restart(); - post_thread = std::thread(post_thread_handler); + fetch_more_previous_messages_if_needed(); } else { go_to_previous_page = true; diff --git a/src/Text.cpp b/src/Text.cpp index 9c6bc86..b463f92 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -615,11 +615,8 @@ namespace QuickMedia int Text::getStartOfWord(int startIndex) const { assert(!dirty && !dirtyText); - int start_line = get_vertex_line(startIndex); bool start_is_special_char = is_special_character(get_vertex_codepoint(startIndex - 1)); for(int i = startIndex - 1; i >= 0; --i) { - if(get_vertex_line(i) != start_line) - return i + 1; bool is_special_char = is_special_character(vertices_linear[i].codepoint); if(is_special_char != start_is_special_char) return i + 1; @@ -630,11 +627,8 @@ namespace QuickMedia int Text::getEndOfWord(int startIndex) const { assert(!dirty && !dirtyText); const int num_vertices = vertices_linear.size(); - int start_line = get_vertex_line(startIndex); bool start_is_special_char = is_special_character(get_vertex_codepoint(startIndex)); for(int i = startIndex + 1; i < num_vertices; ++i) { - if(get_vertex_line(i) != start_line) - return i - 1; bool is_special_char = is_special_character(vertices_linear[i].codepoint); if(is_special_char != start_is_special_char) return i; diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index 4b78978..446cdf1 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -1056,10 +1056,8 @@ namespace QuickMedia { } } - if(next_batch.empty()) { - additional_messages_queue.push(true); + if(next_batch.empty()) clear_sync_cache_for_new_sync(); - } result = parse_sync_response(json_root, false); if(result != PluginResult::OK) { @@ -1067,6 +1065,9 @@ namespace QuickMedia { goto sync_end; } + if(next_batch.empty()) + additional_messages_queue.push(true); + next_batch_json = &GetMember(json_root, "next_batch"); if(next_batch_json->IsString()) { set_next_batch(next_batch_json->GetString()); @@ -2376,7 +2377,7 @@ namespace QuickMedia { return "m.file"; } - PluginResult Matrix::post_message(RoomData *room, const std::string &body, const std::optional<UploadInfo> &file_info, const std::optional<UploadInfo> &thumbnail_info, const std::string &msgtype) { + PluginResult Matrix::post_message(RoomData *room, const std::string &body, std::string &event_id_response, const std::optional<UploadInfo> &file_info, const std::optional<UploadInfo> &thumbnail_info, const std::string &msgtype) { char random_characters[18]; if(!generate_random_characters(random_characters, sizeof(random_characters))) return PluginResult::ERR; @@ -2473,6 +2474,7 @@ namespace QuickMedia { return PluginResult::ERR; fprintf(stderr, "Matrix post message, response event id: %s\n", event_id_json.GetString()); + event_id_response = std::string(event_id_json.GetString(), event_id_json.GetStringLength()); return PluginResult::OK; } @@ -2557,7 +2559,7 @@ namespace QuickMedia { } // TODO: Support greentext - PluginResult Matrix::post_reply(RoomData *room, const std::string &body, void *relates_to) { + PluginResult Matrix::post_reply(RoomData *room, const std::string &body, void *relates_to, std::string &event_id_response) { // TODO: Store shared_ptr<Message> instead of raw pointer... Message *relates_to_message_raw = (Message*)relates_to; @@ -2609,10 +2611,11 @@ namespace QuickMedia { return PluginResult::ERR; fprintf(stderr, "Matrix post reply, response event id: %s\n", event_id_json.GetString()); + event_id_response = std::string(event_id_json.GetString(), event_id_json.GetStringLength()); return PluginResult::OK; } - PluginResult Matrix::post_edit(RoomData *room, const std::string &body, void *relates_to) { + PluginResult Matrix::post_edit(RoomData *room, const std::string &body, void *relates_to, std::string &event_id_response) { Message *relates_to_message_raw = (Message*)relates_to; char random_characters[18]; @@ -2693,6 +2696,7 @@ namespace QuickMedia { return PluginResult::ERR; fprintf(stderr, "Matrix post edit, response event id: %s\n", event_id_json.GetString()); + event_id_response = std::string(event_id_json.GetString(), event_id_json.GetStringLength()); return PluginResult::OK; } @@ -2704,7 +2708,7 @@ namespace QuickMedia { auto fetched_message_it = room->fetched_messages_by_event_id.find(event_id); if(fetched_message_it != room->fetched_messages_by_event_id.end()) return fetched_message_it->second; - +#if 0 rapidjson::Document request_data(rapidjson::kObjectType); request_data.AddMember("lazy_load_members", true, request_data.GetAllocator()); @@ -2720,7 +2724,14 @@ namespace QuickMedia { char url[512]; snprintf(url, sizeof(url), "%s/_matrix/client/r0/rooms/%s/context/%s?limit=0&filter=%s", homeserver.c_str(), room->id.c_str(), event_id.c_str(), filter.c_str()); +#else + std::vector<CommandArg> additional_args = { + { "-H", "Authorization: Bearer " + access_token } + }; + char url[512]; + snprintf(url, sizeof(url), "%s/_matrix/client/r0/rooms/%s/event/%s", homeserver.c_str(), room->id.c_str(), event_id.c_str()); +#endif std::string err_msg; rapidjson::Document json_root; DownloadResult download_result = download_json(json_root, url, std::move(additional_args), true, &err_msg); @@ -2738,13 +2749,17 @@ namespace QuickMedia { room->fetched_messages_by_event_id.insert(std::make_pair(event_id, nullptr)); return nullptr; } - +#if 0 const rapidjson::Value &state_json = GetMember(json_root, "state"); events_add_user_info(state_json, room); +#endif //events_set_room_name(state_json, room); - +#if 0 const rapidjson::Value &event_json = GetMember(json_root, "event"); std::shared_ptr<Message> new_message = parse_message_event(event_json, room); +#else + std::shared_ptr<Message> new_message = parse_message_event(json_root, room); +#endif room->fetched_messages_by_event_id.insert(std::make_pair(event_id, new_message)); return new_message; } @@ -2756,7 +2771,7 @@ namespace QuickMedia { return filepath.c_str() + index + 1; } - PluginResult Matrix::post_file(RoomData *room, const std::string &filepath, std::string &err_msg) { + PluginResult Matrix::post_file(RoomData *room, const std::string &filepath, std::string &event_id_response, std::string &err_msg) { UploadInfo file_info; UploadInfo thumbnail_info; PluginResult upload_file_result = upload_file(room, filepath, file_info, thumbnail_info, err_msg); @@ -2769,7 +2784,7 @@ namespace QuickMedia { thumbnail_info_opt = std::move(thumbnail_info); const char *filename = file_get_filename(filepath); - return post_message(room, filename, file_info_opt, thumbnail_info_opt); + return post_message(room, filename, event_id_response, file_info_opt, thumbnail_info_opt); } PluginResult Matrix::upload_file(RoomData *room, const std::string &filepath, UploadInfo &file_info, UploadInfo &thumbnail_info, std::string &err_msg) { |