aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Body.cpp4
-rw-r--r--src/QuickMedia.cpp357
-rw-r--r--src/Tabs.cpp6
-rw-r--r--src/plugins/Matrix.cpp194
4 files changed, 429 insertions, 132 deletions
diff --git a/src/Body.cpp b/src/Body.cpp
index b7424c5..f9a3717 100644
--- a/src/Body.cpp
+++ b/src/Body.cpp
@@ -1581,6 +1581,7 @@ namespace QuickMedia {
has_loaded_text = true;
}
+ // TODO: Only do this if reactions dirty?
if(!item->reactions.empty() && include_embedded_item) {
float reaction_offset_x = 0.0f;
item_height += body_spacing[body_theme].reaction_padding_y;
@@ -1613,7 +1614,8 @@ namespace QuickMedia {
const bool has_thumbnail = draw_thumbnails && !item->thumbnail_url.empty() && !merge_with_previous;
const float padding_y = has_thumbnail ? body_spacing[body_theme].padding_y : body_spacing[body_theme].padding_y_text_only;
item_height = std::max(item_height, item->loaded_image_size.y);
- item_height += (padding_y * 2.0f);
+ if(item_height > 0.0f)
+ item_height += (padding_y * 2.0f);
}
item->loaded_height = item_height;
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index 050ae01..f809b70 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -1885,6 +1885,7 @@ namespace QuickMedia {
video_content_page(tabs[selected_tab].page.get(), static_cast<VideoPage*>(new_tabs[0].page.get()), "", false, tabs[selected_tab].body.get(), selected_index, &tab_associated_data[selected_tab].fetched_page, tab_associated_data[selected_tab].update_search_text);
} else if(new_tabs.size() == 1 && new_tabs[0].page->get_type() == PageTypez::CHAT) {
MatrixChatPage *tmp_matrix_chat_page = static_cast<MatrixChatPage*>(new_tabs[0].page.get());
+ std::string jump_to_event_id = tmp_matrix_chat_page->jump_to_event_id;
MatrixRoomsPage *rooms_page = tmp_matrix_chat_page->rooms_page;
Body *room_list_body = rooms_page->body;
rooms_page->clear_search();
@@ -1895,7 +1896,7 @@ namespace QuickMedia {
rooms_page->body->show_drop_shadow = false;
while(window.isOpen() && current_chat_room) {
- auto matrix_chat_page = std::make_unique<MatrixChatPage>(this, current_chat_room->id, rooms_page);
+ auto matrix_chat_page = std::make_unique<MatrixChatPage>(this, current_chat_room->id, rooms_page, jump_to_event_id);
bool move_room = chat_page(matrix_chat_page.get(), current_chat_room);
matrix_chat_page->messages_tab_visible = false;
if(!move_room)
@@ -1906,6 +1907,7 @@ namespace QuickMedia {
break;
current_chat_room = matrix->get_room_by_id(selected_item->url);
+ jump_to_event_id.clear();
}
rooms_page->body->show_drop_shadow = true;
@@ -3768,7 +3770,6 @@ namespace QuickMedia {
sf::Text captcha_solution_text("", *FontLoader::get_font(FontLoader::FontType::LATIN_BOLD), captcha_solution_text_height);
int solved_captcha_ttl = 0;
int64_t last_posted_time = time(nullptr);
- fprintf(stderr, "unix time: %ld\n", last_posted_time);
int64_t seconds_until_post_again = 60; // TODO: Timeout for other imageboards
bool has_post_timeout = thread_page->get_pass_id().empty();
@@ -4545,6 +4546,22 @@ namespace QuickMedia {
return nullptr;
}
+ // TODO: Optimize
+ static std::shared_ptr<BodyItem> find_body_item_by_event_id(BodyItemList body_items, const std::string &event_id, int *index_result) {
+ if(event_id.empty())
+ return nullptr;
+
+ for(int i = 0; i < (int)body_items.size(); ++i) {
+ auto &body_item = body_items[i];
+ if(body_item->userdata && static_cast<Message*>(body_item->userdata)->event_id == event_id) {
+ if(index_result)
+ *index_result = i;
+ return body_item;
+ }
+ }
+ return nullptr;
+ }
+
// Returns true if cached and loaded
static bool load_cached_related_embedded_item(BodyItem *body_item, Message *message, UserInfo *me, const std::string &my_display_name, const std::string &my_user_id, const BodyItemList &message_body_items) {
// Check if we already have the referenced message as a body item, so we dont create a new one.
@@ -4683,7 +4700,7 @@ namespace QuickMedia {
return replaces;
replaces = replaces->replaces;
}
- return nullptr;
+ return message;
}
bool Program::chat_page(MatrixChatPage *matrix_chat_page, RoomData *current_room) {
@@ -5398,7 +5415,13 @@ namespace QuickMedia {
return false;
};
- AsyncTask<Messages> previous_messages_future;
+ struct FetchMessagesResult {
+ Messages messages;
+ MessageDirection message_dir;
+ bool reached_end = false;
+ };
+ AsyncTask<FetchMessagesResult> fetch_messages_future;
+ MessageDirection fetch_messages_dir = MessageDirection::BEFORE;
enum class FetchMessageType {
MESSAGE,
@@ -5533,20 +5556,11 @@ namespace QuickMedia {
sf::Vertex gradient_points[4];
double gradient_inc = 0;
- bool fetched_enough_messages = false;
-
- auto fetch_more_previous_messages_if_needed = [this, &tabs, &current_room, &fetched_enough_messages, &previous_messages_future, MESSAGES_TAB_INDEX]() {
- if(!fetched_enough_messages && !previous_messages_future.valid()) {
- if(tabs[MESSAGES_TAB_INDEX].body->get_num_items() < 30) {
- previous_messages_future = AsyncTask<Messages>([this, &current_room]() {
- Messages messages;
- if(matrix->get_previous_room_messages(current_room, messages) != PluginResult::OK)
- fprintf(stderr, "Failed to get previous matrix messages in room: %s\n", current_room->id.c_str());
- return messages;
- });
- }
- }
- };
+ std::string before_token;
+ std::string after_token;
+ bool fetched_enough_messages_top = false;
+ bool fetched_enough_messages_bottom = false;
+ bool has_unread_messages = false;
sf::RectangleShape more_messages_below_rect;
more_messages_below_rect.setFillColor(get_current_theme().new_items_alert_color);
@@ -5566,6 +5580,80 @@ namespace QuickMedia {
bool avatar_applied = false;
+ auto jump_to_message = [&](const std::string &event_id) {
+ const int selected_tab = ui_tabs.get_selected();
+ if(selected_tab != MESSAGES_TAB_INDEX || event_id.empty())
+ return false;
+
+ int body_item_index = -1;
+ auto body_item = find_body_item_by_event_id(tabs[MESSAGES_TAB_INDEX].body->get_items(), event_id, &body_item_index);
+ if(body_item) {
+ tabs[MESSAGES_TAB_INDEX].body->set_selected_item(body_item_index, false);
+ return true;
+ } else {
+ std::shared_ptr<Message> fetched_message;
+ Messages before_messages;
+ Messages after_messages;
+ std::string new_before_token;
+ std::string new_after_token;
+ TaskResult task_result = run_task_with_loading_screen([this, current_room, &event_id, &fetched_message, &before_messages, &after_messages, &new_before_token, &new_after_token]{
+ std::string message_to_fetch = event_id;
+ for(int i = 0; i < 10; ++i) { // TODO: Delay between tries?
+ auto message = matrix->get_message_by_id(current_room, message_to_fetch);
+ if(!message)
+ return false;
+
+ message_to_fetch = message->event_id;
+ if(message->related_event_type != RelatedEventType::EDIT)
+ break;
+ }
+ return matrix->get_message_context(current_room, message_to_fetch, fetched_message, before_messages, after_messages, new_before_token, new_after_token) == PluginResult::OK;
+ });
+
+ if(task_result == TaskResult::TRUE) {
+ Messages this_message = { fetched_message };
+ Messages *messages[3] = {
+ &before_messages,
+ &this_message,
+ &after_messages
+ };
+
+ before_token = std::move(new_before_token);
+ after_token = std::move(new_after_token);
+
+ fetch_messages_future.cancel();
+ fetched_enough_messages_top = false;
+ fetched_enough_messages_bottom = false;
+ unresolved_reactions.clear();
+ unreferenced_events.clear();
+ sent_messages.clear();
+ fetched_messages_set.clear();
+ tabs[MESSAGES_TAB_INDEX].body->clear_items();
+ matrix->clear_previous_messages_token(current_room);
+
+ for(Messages *message_list : messages) {
+ for(auto &message : *message_list) {
+ fetched_messages_set.insert(message->event_id);
+ }
+
+ all_messages.insert(all_messages.end(), message_list->begin(), message_list->end());
+ auto new_body_items = messages_to_body_items(current_room, *message_list, current_room->get_user_display_name(me), me->user_id);
+ messages_load_cached_related_embedded_item(new_body_items, tabs[MESSAGES_TAB_INDEX].body->get_items(), me, current_room);
+ tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps(std::move(new_body_items));
+ modify_related_messages_in_current_room(*message_list);
+ process_reactions(*message_list);
+ }
+
+ int fetched_message_index = -1;
+ find_body_item_by_event_id(tabs[MESSAGES_TAB_INDEX].body->get_items(), fetched_message->event_id, &fetched_message_index);
+ tabs[MESSAGES_TAB_INDEX].body->set_selected_item(fetched_message_index, false);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ };
+
auto launch_url = [this, matrix_chat_page, &tabs, MESSAGES_TAB_INDEX, &redraw, &avatar_applied](std::string url) mutable {
if(url.empty())
return;
@@ -5610,7 +5698,7 @@ namespace QuickMedia {
}
};
- auto add_new_messages_to_current_room = [&me, &tabs, &ui_tabs, &current_room, MESSAGES_TAB_INDEX](Messages &messages) {
+ auto add_new_messages_to_current_room = [&me, &tabs, &ui_tabs, &current_room, MESSAGES_TAB_INDEX, &after_token](Messages &messages) {
if(messages.empty())
return;
@@ -5626,6 +5714,9 @@ namespace QuickMedia {
scroll_to_end = true;
}
+ if(!after_token.empty())
+ scroll_to_end = false;
+
auto new_body_items = messages_to_body_items(current_room, messages, current_room->get_user_display_name(me), me->user_id);
messages_load_cached_related_embedded_item(new_body_items, tabs[MESSAGES_TAB_INDEX].body->get_items(), me, current_room);
tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps(std::move(new_body_items));
@@ -5810,21 +5901,64 @@ namespace QuickMedia {
move_room = true;
};
- std::function<void()> on_top_reached = [this, &previous_messages_future, &ui_tabs, &MESSAGES_TAB_INDEX, &gradient_inc, current_room] {
+ std::function<void()> on_top_reached = [this, &fetch_messages_future, &fetch_messages_dir, &ui_tabs, &MESSAGES_TAB_INDEX, &gradient_inc, current_room, &before_token, &after_token, &fetched_enough_messages_top] {
const int selected_tab = ui_tabs.get_selected();
- if(!previous_messages_future.valid() && selected_tab == MESSAGES_TAB_INDEX) {
- gradient_inc = 0;
- previous_messages_future = AsyncTask<Messages>([this, current_room]() {
- Messages messages;
- if(matrix->get_previous_room_messages(current_room, messages) != PluginResult::OK)
+ if(fetched_enough_messages_top || fetch_messages_future.valid() || selected_tab != MESSAGES_TAB_INDEX)
+ return;
+
+ gradient_inc = 0;
+ if(before_token.empty() && after_token.empty()) {
+ fetch_messages_dir = MessageDirection::BEFORE;
+ fetch_messages_future = AsyncTask<FetchMessagesResult>([this, current_room]() {
+ FetchMessagesResult messages;
+ messages.message_dir = MessageDirection::BEFORE;
+ messages.reached_end = false;
+ if(matrix->get_previous_room_messages(current_room, messages.messages, false, &messages.reached_end) != PluginResult::OK)
+ fprintf(stderr, "Failed to get previous matrix messages in room: %s\n", current_room->id.c_str());
+ return messages;
+ });
+ } else if(!before_token.empty()) {
+ fetch_messages_dir = MessageDirection::BEFORE;
+ fetch_messages_future = AsyncTask<FetchMessagesResult>([this, current_room, &before_token]() {
+ std::string token = before_token;
+ FetchMessagesResult messages;
+ messages.message_dir = MessageDirection::BEFORE;
+ messages.reached_end = false;
+ if(matrix->get_messages_in_direction(current_room, token, MessageDirection::BEFORE, messages.messages, before_token) != PluginResult::OK)
fprintf(stderr, "Failed to get previous matrix messages in room: %s\n", current_room->id.c_str());
+ messages.reached_end = before_token.empty();
+ return messages;
+ });
+ }
+ };
+
+ std::function<void()> on_bottom_reached = [this, &fetch_messages_future, &fetch_messages_dir, &ui_tabs, &MESSAGES_TAB_INDEX, &gradient_inc, current_room, &after_token, &fetched_enough_messages_bottom] {
+ const int selected_tab = ui_tabs.get_selected();
+ if(!fetched_enough_messages_bottom && !after_token.empty() && !fetch_messages_future.valid() && selected_tab == MESSAGES_TAB_INDEX) {
+ fetch_messages_dir = MessageDirection::AFTER;
+ gradient_inc = 0;
+ fetch_messages_future = AsyncTask<FetchMessagesResult>([this, current_room, &after_token]() {
+ std::string token = after_token;
+ FetchMessagesResult messages;
+ messages.message_dir = MessageDirection::AFTER;
+ messages.reached_end = false;
+ if(matrix->get_messages_in_direction(current_room, token, MessageDirection::AFTER, messages.messages, after_token) != PluginResult::OK)
+ fprintf(stderr, "Failed to get next matrix messages in room: %s\n", current_room->id.c_str());
+ messages.reached_end = after_token.empty();
return messages;
});
+ return;
}
};
for(size_t i = 0; i < tabs.size(); ++i) {
tabs[i].body->on_top_reached = on_top_reached;
+ tabs[i].body->on_bottom_reached = on_bottom_reached;
+ }
+
+ if(!matrix_chat_page->jump_to_event_id.empty()) {
+ if(!jump_to_message(matrix_chat_page->jump_to_event_id))
+ goto chat_page_end;
}
while (current_page == PageType::CHAT && window.isOpen() && !move_room) {
@@ -5972,11 +6106,22 @@ namespace QuickMedia {
upload_file(std::string(clipboard.begin(), clipboard.end()));
}
- if(event.key.code == sf::Keyboard::R) {
- frame_skip_text_entry = true;
+ if(event.key.code == sf::Keyboard::R && tabs[selected_tab].body->get_selected_shared()) {
std::shared_ptr<BodyItem> selected = tabs[selected_tab].body->get_selected_shared();
- if(selected) {
- if(!is_state_message_type(static_cast<Message*>(selected->userdata))) {
+ Message *selected_message = static_cast<Message*>(selected->userdata);
+ const bool go_to_replied_message = event.key.control;
+ if(!is_state_message_type(selected_message)) {
+ if(go_to_replied_message && selected_message->related_event_type == RelatedEventType::REPLY) {
+ int replied_to_body_item_index = -1;
+ auto replied_to_body_item = find_body_item_by_event_id(tabs[MESSAGES_TAB_INDEX].body->get_items(), selected_message->related_event_id, &replied_to_body_item_index);
+ if(replied_to_body_item) {
+ assert(replied_to_body_item->userdata);
+ Message *orig_message = get_original_message(static_cast<Message*>(replied_to_body_item->userdata));
+ jump_to_message(orig_message->event_id);
+ } else {
+ jump_to_message(selected_message->related_event_id);
+ }
+ } else if(!go_to_replied_message) {
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");
@@ -5985,14 +6130,18 @@ namespace QuickMedia {
currently_operating_on_item = selected;
chat_input.set_editable(true);
replying_to_text.setString("Replying to:");
+ frame_skip_text_entry = true;
}
}
- } else {
- // TODO: Show inline notification
- show_notification("QuickMedia", "No message selected for replying");
}
}
+ if(event.key.code == sf::Keyboard::B && event.key.control) {
+ // Reload room, goes to latest message l0l
+ move_room = true;
+ goto chat_page_end;
+ }
+
if(event.key.code == sf::Keyboard::E) {
frame_skip_text_entry = true;
std::shared_ptr<BodyItem> selected = tabs[selected_tab].body->get_selected_shared();
@@ -6086,6 +6235,15 @@ namespace QuickMedia {
show_notification("QuickMedia", "No message selected for unpinning");
}
}
+
+ if(event.key.code == sf::Keyboard::R && event.key.control && tabs[selected_tab].body->get_selected_shared()) {
+ std::shared_ptr<BodyItem> selected = tabs[selected_tab].body->get_selected_shared();
+ PinnedEventData *selected_event_data = static_cast<PinnedEventData*>(selected->userdata);
+ if(selected_event_data && !selected_event_data->event_id.empty()) {
+ ui_tabs.set_selected(MESSAGES_TAB_INDEX);
+ jump_to_message(selected_event_data->event_id);
+ }
+ }
}
} else if(event.type == sf::Event::KeyPressed && chat_state == ChatState::URL_SELECTION) {
if(event.key.code == sf::Keyboard::Escape) {
@@ -6189,7 +6347,7 @@ namespace QuickMedia {
}
case PageType::CHAT_LOGIN: {
matrix_chat_page->set_current_room(nullptr, nullptr);
- previous_messages_future.cancel();
+ fetch_messages_future.cancel();
cleanup_tasks();
tabs.clear();
unreferenced_events.clear();
@@ -6314,14 +6472,22 @@ namespace QuickMedia {
sync_data.messages.clear();
sync_data.pinned_events = std::nullopt;
matrix->get_room_sync_data(current_room, sync_data);
- if(!sync_data.messages.empty()) {
+ if(!sync_data.messages.empty() && after_token.empty()) {
all_messages.insert(all_messages.end(), sync_data.messages.begin(), sync_data.messages.end());
filter_existing_messages(sync_data.messages);
}
filter_provisional_messages(sync_data.messages);
- add_new_messages_to_current_room(sync_data.messages);
- modify_related_messages_in_current_room(sync_data.messages);
- process_reactions(sync_data.messages);
+ if(after_token.empty()) {
+ add_new_messages_to_current_room(sync_data.messages);
+ modify_related_messages_in_current_room(sync_data.messages);
+ process_reactions(sync_data.messages);
+ } else {
+ auto it = std::find_if(sync_data.messages.begin(), sync_data.messages.end(), [](const std::shared_ptr<Message> &message) {
+ return message->related_event_type == RelatedEventType::NONE || message->related_event_type == RelatedEventType::REPLY;
+ });
+ if(it != sync_data.messages.end())
+ has_unread_messages = true;
+ }
process_pinned_events(sync_data.pinned_events);
if(set_read_marker_future.ready()) {
@@ -6330,20 +6496,22 @@ namespace QuickMedia {
setting_read_marker = false;
}
- 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()) {
- fetched_enough_messages = true;
- }
- filter_sent_messages(new_messages);
- filter_existing_messages(new_messages);
- fprintf(stderr, "Finished fetching older messages, num new messages: %zu\n", new_messages.size());
- size_t num_new_messages = new_messages.size();
+ if(fetch_messages_future.ready()) {
+ FetchMessagesResult new_messages_result = fetch_messages_future.get();
+ all_messages.insert(all_messages.end(), new_messages_result.messages.begin(), new_messages_result.messages.end());
+
+ if(new_messages_result.message_dir == MessageDirection::BEFORE)
+ fetched_enough_messages_top = new_messages_result.reached_end;
+ if(new_messages_result.message_dir == MessageDirection::AFTER)
+ fetched_enough_messages_bottom = new_messages_result.reached_end;
+
+ filter_sent_messages(new_messages_result.messages);
+ filter_existing_messages(new_messages_result.messages);
+ size_t num_new_messages = new_messages_result.messages.size();
if(num_new_messages > 0) {
- add_new_messages_to_current_room(new_messages);
- modify_related_messages_in_current_room(new_messages);
- process_reactions(new_messages);
+ add_new_messages_to_current_room(new_messages_result.messages);
+ modify_related_messages_in_current_room(new_messages_result.messages);
+ process_reactions(new_messages_result.messages);
// TODO: Do not loop all items, only loop the new items
resolve_unreferenced_events_with_body_items(tabs[MESSAGES_TAB_INDEX].body->get_items().data(), tabs[MESSAGES_TAB_INDEX].body->get_items().size());
}
@@ -6437,42 +6605,14 @@ namespace QuickMedia {
sf::RectangleShape room_list_background(sf::Vector2f(this->body_size.x, window_size.y));
//room_list_background.setPosition(this->body_pos);
room_list_background.setFillColor(get_current_theme().shade_color);
- glEnable(GL_SCISSOR_TEST);
- glScissor(0.0f, 0.0f, this->body_size.x, window_size.y);
window.draw(room_list_background);
window.draw(room_label);
const float tab_y = std::floor(tab_vertical_offset) + room_name_padding_y;
matrix_chat_page->rooms_page->body->draw(window, sf::Vector2f(0.0f, tab_y), sf::Vector2f(this->body_size.x, window_size.y - tab_y), Json::Value::nullSingleton());
- glDisable(GL_SCISSOR_TEST);
}
ui_tabs.draw(window, sf::Vector2f(body_pos.x, std::floor(tab_vertical_offset) + room_name_padding_y), body_size.x);
- // TODO: Have one for each room. Also add bottom one? for fetching new messages (currently not implemented, is it needed?)
- if(previous_messages_future.valid() && 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(get_current_theme().background_color, get_current_theme().loading_page_color, progress);
-
- gradient_points[0].position.x = chat_input_shade.getPosition().x;
- gradient_points[0].position.y = tab_shade_height;
-
- gradient_points[1].position.x = window_size.x;
- gradient_points[1].position.y = tab_shade_height;
-
- gradient_points[2].position.x = window_size.x;
- gradient_points[2].position.y = tab_shade_height + gradient_height;
-
- gradient_points[3].position.x = chat_input_shade.getPosition().x;
- gradient_points[3].position.y = tab_shade_height + gradient_height;
-
- gradient_points[0].color = top_color;
- gradient_points[1].color = top_color;
- gradient_points[2].color = get_current_theme().background_color;
- gradient_points[3].color = get_current_theme().background_color;
- window.draw(gradient_points, 4, sf::Quads); // Note: sf::Quads doesn't work with egl
- }
-
if(chat_state == ChatState::REPLYING || chat_state == ChatState::EDITING) {
const float margin = 5.0f;
const float replying_to_text_height = replying_to_text.getLocalBounds().height + margin;
@@ -6515,8 +6655,8 @@ namespace QuickMedia {
tabs[MESSAGES_TAB_INDEX].body->draw_item(window, currently_operating_on_item, body_item_pos, body_item_size);
}
- if(selected_tab == MESSAGES_TAB_INDEX && current_room && current_room->body_item && !current_room->last_message_read && matrix->is_initial_sync_finished()) {
- if(!tabs[selected_tab].body->is_bottom_cut_off() && is_window_focused && chat_state != ChatState::URL_SELECTION && !setting_read_marker && read_marker_timer.getElapsedTime().asMilliseconds() >= read_marker_timeout_ms) {
+ if(selected_tab == MESSAGES_TAB_INDEX && current_room && current_room->body_item && (!current_room->last_message_read || has_unread_messages) && matrix->is_initial_sync_finished()) {
+ if(after_token.empty() && !tabs[selected_tab].body->is_bottom_cut_off() && is_window_focused && chat_state != ChatState::URL_SELECTION && !setting_read_marker && read_marker_timer.getElapsedTime().asMilliseconds() >= read_marker_timeout_ms) {
auto body_items = tabs[selected_tab].body->get_items();
int last_timeline_message = (int)body_items.size() - 1;
for(int i = last_timeline_message - 1; i >= 0; --i) {
@@ -6564,12 +6704,51 @@ namespace QuickMedia {
});
}
}
- } else if(tabs[selected_tab].body->is_bottom_cut_off()) {
+ } else if(tabs[selected_tab].body->is_bottom_cut_off() || has_unread_messages) {
window.draw(more_messages_below_rect);
}
}
- if(selected_tab == MESSAGES_TAB_INDEX && current_room) {
+ // TODO: Have one for each room. Also add bottom one? for fetching new messages (currently not implemented, is it needed?)
+ if(fetch_messages_future.valid() && selected_tab == MESSAGES_TAB_INDEX) {
+ // TODO: fetch_messages_dir
+ 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(get_current_theme().background_color, get_current_theme().loading_page_color, progress);
+
+ gradient_points[0].position.x = chat_input_shade.getPosition().x;
+ gradient_points[0].position.y = tab_shade_height;
+
+ gradient_points[1].position.x = window_size.x;
+ gradient_points[1].position.y = tab_shade_height;
+
+ gradient_points[2].position.x = window_size.x;
+ gradient_points[2].position.y = tab_shade_height + gradient_height;
+
+ gradient_points[3].position.x = chat_input_shade.getPosition().x;
+ gradient_points[3].position.y = tab_shade_height + gradient_height;
+
+ gradient_points[0].color = top_color;
+ gradient_points[1].color = top_color;
+ gradient_points[2].color = get_current_theme().background_color;
+ gradient_points[3].color = get_current_theme().background_color;
+
+ if(fetch_messages_dir == MessageDirection::AFTER) {
+ gradient_points[0].position.y = more_messages_below_rect.getPosition().y;
+ gradient_points[1].position.y = more_messages_below_rect.getPosition().y;
+ gradient_points[2].position.y = more_messages_below_rect.getPosition().y + gradient_height;
+ gradient_points[3].position.y = more_messages_below_rect.getPosition().y + gradient_height;
+
+ gradient_points[0].color = get_current_theme().background_color;
+ gradient_points[1].color = get_current_theme().background_color;
+ gradient_points[2].color = top_color;
+ gradient_points[3].color = top_color;
+ }
+
+ window.draw(gradient_points, 4, sf::Quads); // Note: sf::Quads doesn't work with egl
+ }
+
+ if(selected_tab == MESSAGES_TAB_INDEX) {
//window.draw(chat_input_shade);
chat_input.draw(window); //chat_input.draw(window, false);
window.draw(logo_sprite);
@@ -6579,7 +6758,7 @@ namespace QuickMedia {
std::string err_msg;
if(matrix->did_initial_sync_fail(err_msg)) {
matrix_chat_page->set_current_room(nullptr, nullptr);
- previous_messages_future.cancel();
+ fetch_messages_future.cancel();
cleanup_tasks();
tabs.clear();
unreferenced_events.clear();
@@ -6598,8 +6777,12 @@ namespace QuickMedia {
AsyncImageLoader::get_instance().update();
window.display();
- if(selected_tab == MESSAGES_TAB_INDEX)
- fetch_more_previous_messages_if_needed();
+ if(selected_tab == MESSAGES_TAB_INDEX) {
+ if(!tabs[selected_tab].body->is_top_cut_off())
+ tabs[selected_tab].body->on_top_reached();
+ if(!tabs[selected_tab].body->is_bottom_cut_off())
+ tabs[selected_tab].body->on_bottom_reached();
+ }
if(matrix_chat_page->should_clear_data) {
matrix_chat_page->should_clear_data = false;
@@ -6609,7 +6792,7 @@ namespace QuickMedia {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
if(matrix->did_initial_sync_fail(err_msg)) {
matrix_chat_page->set_current_room(nullptr, nullptr);
- previous_messages_future.cancel();
+ fetch_messages_future.cancel();
cleanup_tasks();
tabs.clear();
unreferenced_events.clear();
@@ -6659,7 +6842,7 @@ namespace QuickMedia {
chat_page_end:
matrix_chat_page->set_current_room(nullptr, nullptr);
- previous_messages_future.cancel();
+ fetch_messages_future.cancel();
cleanup_tasks();
window.setTitle("QuickMedia - matrix");
return move_room;
diff --git a/src/Tabs.cpp b/src/Tabs.cpp
index 62a6fe4..9237d3b 100644
--- a/src/Tabs.cpp
+++ b/src/Tabs.cpp
@@ -212,11 +212,7 @@ namespace QuickMedia {
}
void Tabs::set_selected(int index) {
- if(tabs.empty()) {
- selected_tab = 0;
- } else {
- selected_tab = std::min(std::max(index, 0), (int)tabs.size() - 1);
- }
+ move_selected_tab(index);
}
int Tabs::get_selected() const {
diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp
index 8c7c884..df4fa52 100644
--- a/src/plugins/Matrix.cpp
+++ b/src/plugins/Matrix.cpp
@@ -182,7 +182,7 @@ namespace QuickMedia {
user->avatar_url = std::move(avatar_url);
}
- size_t RoomData::prepend_messages_reverse(const std::vector<std::shared_ptr<Message>> &new_messages) {
+ size_t RoomData::prepend_messages_reverse(const Messages &new_messages) {
std::lock_guard<std::recursive_mutex> lock(room_mutex);
int64_t last_new_message_timestamp = last_message_timestamp;
size_t num_new_messages = 0;
@@ -199,7 +199,7 @@ namespace QuickMedia {
return num_new_messages;
}
- size_t RoomData::append_messages(const std::vector<std::shared_ptr<Message>> &new_messages) {
+ size_t RoomData::append_messages(const Messages &new_messages) {
std::lock_guard<std::recursive_mutex> lock(room_mutex);
int64_t last_new_message_timestamp = last_message_timestamp;
size_t num_new_messages = 0;
@@ -253,7 +253,7 @@ namespace QuickMedia {
room_mutex.unlock();
}
- const std::vector<std::shared_ptr<Message>>& RoomData::get_messages_thread_unsafe() const {
+ const Messages& RoomData::get_messages_thread_unsafe() const {
return messages;
}
@@ -268,9 +268,7 @@ namespace QuickMedia {
void RoomData::set_prev_batch(const std::string &new_prev_batch) {
std::lock_guard<std::recursive_mutex> lock(room_mutex);
- // TODO: Check if this always works and if it also works for other homeservers than synapse
- if(prev_batch.empty() || new_prev_batch < prev_batch)
- prev_batch = new_prev_batch;
+ prev_batch = new_prev_batch;
}
std::string RoomData::get_prev_batch() {
@@ -884,7 +882,9 @@ namespace QuickMedia {
title = "Invites (0)";
}
- MatrixChatPage::MatrixChatPage(Program *program, std::string room_id, MatrixRoomsPage *rooms_page) : Page(program), room_id(std::move(room_id)), rooms_page(rooms_page) {
+ MatrixChatPage::MatrixChatPage(Program *program, std::string room_id, MatrixRoomsPage *rooms_page, std::string jump_to_event_id) :
+ Page(program), room_id(std::move(room_id)), rooms_page(rooms_page), jump_to_event_id(std::move(jump_to_event_id))
+ {
assert(rooms_page);
rooms_page->set_current_chat_page(this);
rooms_page->matrix_delegate->chat_page = this;
@@ -1059,7 +1059,7 @@ namespace QuickMedia {
return PluginResult::OK;
NotificationsExtraData *extra_data = static_cast<NotificationsExtraData*>(selected_item->extra.get());
- result_tabs.push_back(Tab{nullptr, std::make_unique<MatrixChatPage>(program, extra_data->room->id, all_rooms_page), nullptr});
+ result_tabs.push_back(Tab{nullptr, std::make_unique<MatrixChatPage>(program, extra_data->room->id, all_rooms_page, selected_item->url), nullptr});
return PluginResult::OK;
}
@@ -1450,9 +1450,59 @@ namespace QuickMedia {
room->release_room_lock();
}
- PluginResult Matrix::get_previous_room_messages(RoomData *room, Messages &messages, bool latest_messages) {
+ PluginResult Matrix::get_messages_in_direction(RoomData *room, const std::string &token, MessageDirection message_dir, Messages &messages, std::string &new_token) {
+ // TODO: Retry on failure (after a timeout) instead of setting new token to an empty string
+ new_token.clear();
+
+ rapidjson::Document request_data(rapidjson::kObjectType);
+ request_data.AddMember("lazy_load_members", true, request_data.GetAllocator());
+
+ rapidjson::StringBuffer buffer;
+ rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
+ request_data.Accept(writer);
+
+ std::vector<CommandArg> additional_args = {
+ { "-H", "Authorization: Bearer " + access_token }
+ };
+
+ std::string filter = url_param_encode(buffer.GetString());
+
+ char url[512];
+ snprintf(url, sizeof(url), "%s/_matrix/client/r0/rooms/%s/messages?from=%s&limit=20&dir=%s&filter=%s", homeserver.c_str(), room->id.c_str(), token.c_str(), message_dir == MessageDirection::BEFORE ? "b" : "f", filter.c_str());
+
+ rapidjson::Document json_root;
+ DownloadResult download_result = download_json(json_root, url, std::move(additional_args), true);
+ if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result);
+
+ if(!json_root.IsObject())
+ return PluginResult::ERR;
+
+ const rapidjson::Value &state_json = GetMember(json_root, "state");
+ events_add_user_info(state_json, room);
+ //events_set_room_info(state_json, room_data);
+
+ const rapidjson::Value &chunk_json = GetMember(json_root, "chunk");
+ if(chunk_json.IsArray()) {
+ for(const rapidjson::Value &event_item_json : chunk_json.GetArray()) {
+ std::shared_ptr<Message> new_message = parse_message_event(event_item_json, room);
+ if(new_message)
+ messages.push_back(std::move(new_message));
+ }
+ }
+
+ const rapidjson::Value &end_json = GetMember(json_root, "end");
+ if(end_json.IsString())
+ new_token.assign(end_json.GetString(), end_json.GetStringLength());
+
+ if(new_token == token)
+ new_token.clear();
+
+ return PluginResult::OK;
+ }
+
+ PluginResult Matrix::get_previous_room_messages(RoomData *room, Messages &messages, bool latest_messages, bool *reached_end) {
size_t num_new_messages = 0;
- PluginResult result = get_previous_room_messages(room, latest_messages, num_new_messages);
+ PluginResult result = get_previous_room_messages(room, latest_messages, num_new_messages, reached_end);
if(result != PluginResult::OK)
return result;
@@ -1993,7 +2043,7 @@ namespace QuickMedia {
return 0;
// TODO: Preallocate
- std::vector<std::shared_ptr<Message>> new_messages;
+ Messages new_messages;
auto me = get_me(room_data);
std::string my_display_name = room_data->get_user_display_name(me);
@@ -2839,7 +2889,7 @@ namespace QuickMedia {
}
}
- PluginResult Matrix::get_previous_room_messages(RoomData *room_data, bool latest_messages, size_t &num_new_messages) {
+ PluginResult Matrix::get_previous_room_messages(RoomData *room_data, bool latest_messages, size_t &num_new_messages, bool *reached_end) {
num_new_messages = 0;
std::string from = room_data->get_prev_batch();
if(from.empty() || latest_messages)
@@ -2869,6 +2919,7 @@ namespace QuickMedia {
return PluginResult::ERR;
const rapidjson::Value &state_json = GetMember(json_root, "state");
+ // TODO: Remove?
events_add_user_info(state_json, room_data);
//events_set_room_info(state_json, room_data);
@@ -2879,9 +2930,14 @@ namespace QuickMedia {
if(!end_json.IsString()) {
room_data->set_prev_batch("invalid");
fprintf(stderr, "Warning: matrix messages response is missing 'end', this could happen if we received the very first messages in the room\n");
+ if(reached_end)
+ *reached_end = true;
return PluginResult::OK;
}
+ if(reached_end)
+ *reached_end = strcmp(end_json.GetString(), from.c_str()) == 0;
+
room_data->set_prev_batch(end_json.GetString());
return PluginResult::OK;
}
@@ -3424,30 +3480,14 @@ 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());
-
- rapidjson::StringBuffer buffer;
- rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
- request_data.Accept(writer);
std::vector<CommandArg> additional_args = {
{ "-H", "Authorization: Bearer " + access_token }
};
- std::string filter = url_param_encode(buffer.GetString());
-
- 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 response;
DownloadResult download_result = download_to_string_cache(url, response, std::move(additional_args), true, [](std::string &response) {
rapidjson::Document json_root;
@@ -3491,21 +3531,97 @@ 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_info(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
+
+ // TODO: Do this? what about state apply order?
+ //const rapidjson::Value &state_json = GetMember(json_root, "state");
+ //events_add_user_info(state_json, room);
+
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;
}
+ PluginResult Matrix::get_message_context(RoomData *room, const std::string &event_id, std::shared_ptr<Message> &message, Messages &before_messages, Messages &after_messages, std::string &before_token, std::string &after_token) {
+ rapidjson::Document request_data(rapidjson::kObjectType);
+ request_data.AddMember("lazy_load_members", true, request_data.GetAllocator());
+
+ rapidjson::StringBuffer buffer;
+ rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
+ request_data.Accept(writer);
+
+ std::vector<CommandArg> additional_args = {
+ { "-H", "Authorization: Bearer " + access_token }
+ };
+
+ std::string filter = url_param_encode(buffer.GetString());
+
+ char url[512];
+ snprintf(url, sizeof(url), "%s/_matrix/client/r0/rooms/%s/context/%s?limit=20&filter=%s", homeserver.c_str(), room->id.c_str(), event_id.c_str(), filter.c_str());
+
+ rapidjson::Document json_root;
+ std::string err_msg;
+ DownloadResult download_result = download_json(json_root, url, std::move(additional_args), true, &err_msg);
+ if(download_result != DownloadResult::OK) {
+ show_notification("QuickMedia", "Failed to get message", Urgency::CRITICAL);
+ return download_result_to_plugin_result(download_result);
+ }
+
+ if(!json_root.IsObject()) {
+ show_notification("QuickMedia", "Failed to parse server response", Urgency::CRITICAL);
+ return PluginResult::ERR;
+ }
+
+ const rapidjson::Value &errcode_json = GetMember(json_root, "errcode");
+ if(errcode_json.IsString() && strcmp(errcode_json.GetString(), "M_NOT_FOUND") != 0) {
+ const rapidjson::Value &error_json = GetMember(json_root, "error");
+ if(error_json.IsString()) {
+ show_notification("QuickMedia", "Failed to get message, error: " + std::string(error_json.GetString(), error_json.GetStringLength()), Urgency::CRITICAL);
+ return PluginResult::ERR;
+ }
+ }
+
+ // TODO: Do this? what about state apply order?
+ //const rapidjson::Value &state_json = GetMember(json_root, "state");
+ //events_add_user_info(state_json, room);
+
+ const rapidjson::Value &start_json = GetMember(json_root, "start");
+ const rapidjson::Value &end_json = GetMember(json_root, "end");
+
+ const rapidjson::Value &event_json = GetMember(json_root, "event");
+ const rapidjson::Value &events_before_json = GetMember(json_root, "events_before");
+ const rapidjson::Value &events_after_json = GetMember(json_root, "events_after");
+
+ if(start_json.IsString())
+ before_token.assign(start_json.GetString(), start_json.GetStringLength());
+
+ if(end_json.IsString())
+ after_token.assign(end_json.GetString(), end_json.GetStringLength());
+
+ message = parse_message_event(event_json, room);
+
+ if(events_before_json.IsArray()) {
+ for(const rapidjson::Value &event_item_json : events_before_json.GetArray()) {
+ std::shared_ptr<Message> new_message = parse_message_event(event_item_json, room);
+ if(new_message)
+ before_messages.push_back(std::move(new_message));
+ }
+ }
+
+ if(events_after_json.IsArray()) {
+ for(const rapidjson::Value &event_item_json : events_after_json.GetArray()) {
+ std::shared_ptr<Message> new_message = parse_message_event(event_item_json, room);
+ if(new_message)
+ after_messages.push_back(std::move(new_message));
+ }
+ }
+
+ return PluginResult::OK;
+ }
+
+ void Matrix::clear_previous_messages_token(RoomData *room) {
+ room->set_prev_batch("");
+ }
+
static const char* file_get_filename(const std::string &filepath) {
size_t index = filepath.rfind('/');
if(index == std::string::npos)