From 80b48b270ed66e3557b98d9fc8e82ad868bcde80 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Thu, 20 May 2021 22:33:32 +0200 Subject: Add notifications tab to matrix --- src/Body.cpp | 51 ++++++------ src/QuickMedia.cpp | 128 ++++++++++++++++++++++------- src/SearchBar.cpp | 4 + src/plugins/Matrix.cpp | 212 +++++++++++++++++++++++++++++++++++++++++-------- 4 files changed, 311 insertions(+), 84 deletions(-) (limited to 'src') diff --git a/src/Body.cpp b/src/Body.cpp index 1b1d35a..5a23694 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -288,11 +288,12 @@ namespace QuickMedia { //item_background.set_position(sf::Vector2f(body_pos.x, item_background_target_pos_y)); } - void Body::select_last_item() { + void Body::select_last_item(bool reset_prev_select) { int new_selected_item = std::max(0, (int)items.size() - 1); selected_scrolled = 0.0f; selected_item = new_selected_item; - //prev_selected_item = selected_item; + if(reset_prev_select) + prev_selected_item = selected_item; //page_scroll = 0.0f; clamp_selection(); clamp_selected_item_to_body_count = 1; @@ -312,8 +313,8 @@ namespace QuickMedia { //item_background.set_position(sf::Vector2f(body_pos.x, item_background_target_pos_y)); } - void Body::prepend_items(BodyItems new_items) { - items.insert(items.begin(), std::make_move_iterator(new_items.begin()), std::make_move_iterator(new_items.end())); + void Body::prepend_items_reverse(BodyItems new_items) { + items.insert(items.begin(), std::make_move_iterator(new_items.rbegin()), std::make_move_iterator(new_items.rend())); items_set_dirty(); } @@ -1006,30 +1007,32 @@ namespace QuickMedia { if(body_item->dirty_timestamp) { body_item->dirty_timestamp = false; - //time_t time_now = time(NULL); - //struct tm *now_tm = localtime(&time_now); + if(body_item->get_timestamp() != 0) { + //time_t time_now = time(NULL); + //struct tm *now_tm = localtime(&time_now); - time_t message_timestamp = body_item->get_timestamp() / 1000; - struct tm message_tm; - localtime_r(&message_timestamp, &message_tm); + time_t message_timestamp = body_item->get_timestamp() / 1000; + struct tm message_tm; + localtime_r(&message_timestamp, &message_tm); - //bool is_same_year = message_tm->tm_year == now_tm->tm_year; - - char time_str[128] = {0}; - /* - if(is_same_year) - strftime(time_str, sizeof(time_str) - 1, "%a %b %d %H:%M:%S", message_tm); - else - strftime(time_str, sizeof(time_str) - 1, "%a %b %d %H:%M:%S %Y", message_tm); - */ - strftime(time_str, sizeof(time_str) - 1, "%a %b %d %H:%M", &message_tm); + //bool is_same_year = message_tm->tm_year == now_tm->tm_year; + + char time_str[128] = {0}; + /* + if(is_same_year) + strftime(time_str, sizeof(time_str) - 1, "%a %b %d %H:%M:%S", message_tm); + else + strftime(time_str, sizeof(time_str) - 1, "%a %b %d %H:%M:%S %Y", message_tm); + */ + strftime(time_str, sizeof(time_str) - 1, "%a %b %d %H:%M", &message_tm); - if(body_item->timestamp_text) - body_item->timestamp_text->setString(time_str); - else - body_item->timestamp_text = std::make_unique(time_str, *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(10 * get_ui_scale())); + if(body_item->timestamp_text) + body_item->timestamp_text->setString(time_str); + else + body_item->timestamp_text = std::make_unique(time_str, *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(10 * get_ui_scale())); - body_item->timestamp_text->setFillColor(sf::Color(185, 190, 198, 100)); + body_item->timestamp_text->setFillColor(sf::Color(185, 190, 198, 100)); + } } } diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index a9956d3..13361c6 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -1482,6 +1482,15 @@ namespace QuickMedia { window.draw(tab_associated_data.search_result_text); } + if(!tabs[selected_tab].page->is_ready()) { + sf::Text loading_text("Loading...", *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(30 * get_ui_scale())); + auto text_bounds = loading_text.getLocalBounds(); + loading_text.setPosition( + std::floor(body_pos.x + body_size.x * 0.5f - text_bounds.width * 0.5f), + std::floor(body_pos.y + body_size.y * 0.5f - text_bounds.height * 0.5f)); + window.draw(loading_text); + } + if(matrix) matrix->update(); @@ -1518,6 +1527,8 @@ namespace QuickMedia { bool redraw = true; for(Tab &tab : tabs) { + if(tab.body->attach_side == AttachSide::BOTTOM) + tab.body->select_last_item(); tab.body->thumbnail_max_size = tab.page->get_thumbnail_max_size(); tab.page->on_navigate_to_page(tab.body.get()); } @@ -1591,9 +1602,9 @@ namespace QuickMedia { tabs[selected_tab].search_bar->clear(); tabs[selected_tab].search_bar->onTextUpdateCallback(""); } else { - int selected_item_index = tabs[selected_tab].body->get_selected_item(); - tabs[selected_tab].body->select_first_item(); - tabs[selected_tab].body->set_selected_item(selected_item_index, false); + //int selected_item_index = tabs[selected_tab].body->get_selected_item(); + //tabs[selected_tab].body->select_first_item(); + //tabs[selected_tab].body->set_selected_item(selected_item_index, false); } } @@ -1716,13 +1727,14 @@ namespace QuickMedia { hide_virtual_keyboard(); }; - std::function on_bottom_reached = [&ui_tabs, &tabs, &tab_associated_data, &gradient_inc] { + std::function on_reached_end = [&ui_tabs, &tabs, &tab_associated_data, &gradient_inc] { const int selected_tab = ui_tabs.get_selected(); if(tab_associated_data[selected_tab].fetch_status == FetchStatus::NONE && !tab_associated_data[selected_tab].fetching_next_page_running && !tab_associated_data[selected_tab].fetching_next_page_failed - && !tabs[selected_tab].body->items.empty() - && tabs[selected_tab].page + && (!tabs[selected_tab].search_bar || tabs[selected_tab].search_bar->is_empty()) + && tabs[selected_tab].body->get_num_visible_items() > 0 + && tabs[selected_tab].page->is_ready() && (!tabs[selected_tab].page->is_lazy_fetch_page() || tab_associated_data[selected_tab].lazy_fetch_finished)) { gradient_inc = 0.0; @@ -1746,7 +1758,10 @@ namespace QuickMedia { submit_handler(body_item->get_title()); }; - tab.body->on_bottom_reached = on_bottom_reached; + if(tab.body->attach_side == AttachSide::TOP) + tab.body->on_bottom_reached = on_reached_end; + else if(tab.body->attach_side == AttachSide::BOTTOM) + tab.body->on_top_reached = on_reached_end; TabAssociatedData &associated_data = tab_associated_data[i]; if(tab.search_bar) { @@ -1762,7 +1777,10 @@ namespace QuickMedia { associated_data.search_text_updated = true; } else { tabs[i].body->filter_search_fuzzy(text); - tabs[i].body->select_first_item(); + if(tabs[i].body->attach_side == AttachSide::TOP) + tabs[i].body->select_first_item(); + else if(tabs[i].body->attach_side == AttachSide::BOTTOM) + tabs[i].body->select_last_item(); } associated_data.typing = false; }; @@ -1816,7 +1834,7 @@ namespace QuickMedia { } } else if(event.key.code == sf::Keyboard::T && event.key.control) { BodyItem *selected_item = tabs[selected_tab].body->get_selected(); - if(selected_item && tabs[selected_tab].page && tabs[selected_tab].page->is_trackable()) { + if(selected_item && tabs[selected_tab].page->is_trackable()) { TrackablePage *trackable_page = dynamic_cast(tabs[selected_tab].page.get()); run_task_with_loading_screen([trackable_page, selected_item](){ return trackable_page->track(selected_item->get_title()) == TrackResult::OK; @@ -1857,17 +1875,31 @@ namespace QuickMedia { // TODO: Dont show tabs if there is only one tab get_body_dimensions(window_size, tabs[selected_tab].search_bar.get(), body_pos, body_size, true); - gradient_points[0].position.x = 0.0f; - gradient_points[0].position.y = window_size.y - gradient_height; + if(tabs[selected_tab].body->attach_side == AttachSide::TOP) { + gradient_points[0].position.x = 0.0f; + gradient_points[0].position.y = window_size.y - gradient_height; - gradient_points[1].position.x = window_size.x; - gradient_points[1].position.y = window_size.y - gradient_height; + gradient_points[1].position.x = window_size.x; + gradient_points[1].position.y = window_size.y - gradient_height; - gradient_points[2].position.x = window_size.x; - gradient_points[2].position.y = window_size.y; + gradient_points[2].position.x = window_size.x; + gradient_points[2].position.y = window_size.y; + + gradient_points[3].position.x = 0.0f; + gradient_points[3].position.y = window_size.y; + } else if(tabs[selected_tab].body->attach_side == AttachSide::BOTTOM) { + gradient_points[0].position.x = 0.0f; + gradient_points[0].position.y = body_pos.y; - gradient_points[3].position.x = 0.0f; - gradient_points[3].position.y = window_size.y; + gradient_points[1].position.x = window_size.x; + gradient_points[1].position.y = body_pos.y; + + gradient_points[2].position.x = window_size.x; + gradient_points[2].position.y = body_pos.y + gradient_height; + + gradient_points[3].position.x = 0.0f; + gradient_points[3].position.y = body_pos.y + gradient_height; + } } if(tab_associated_data[selected_tab].fetching_next_page_running) { @@ -1875,15 +1907,22 @@ namespace QuickMedia { gradient_inc += (frame_time_ms * 0.5); sf::Color bottom_color = interpolate_colors(back_color, sf::Color(175, 180, 188), progress); - gradient_points[0].color = back_color; - gradient_points[1].color = back_color; - gradient_points[2].color = bottom_color; - gradient_points[3].color = bottom_color; + if(tabs[selected_tab].body->attach_side == AttachSide::TOP) { + gradient_points[0].color = back_color; + gradient_points[1].color = back_color; + gradient_points[2].color = bottom_color; + gradient_points[3].color = bottom_color; + } else if(tabs[selected_tab].body->attach_side == AttachSide::BOTTOM) { + gradient_points[0].color = bottom_color; + gradient_points[1].color = bottom_color; + gradient_points[2].color = back_color; + gradient_points[3].color = back_color; + } } if(tabs[selected_tab].search_bar) tabs[selected_tab].search_bar->update(); - if(tabs[selected_tab].page->is_lazy_fetch_page() && tab_associated_data[selected_tab].fetch_status == FetchStatus::NONE && !tab_associated_data[selected_tab].lazy_fetch_finished) { + if(tabs[selected_tab].page->is_ready() && tabs[selected_tab].page->is_lazy_fetch_page() && tab_associated_data[selected_tab].fetch_status == FetchStatus::NONE && !tab_associated_data[selected_tab].lazy_fetch_finished) { tab_associated_data[selected_tab].fetch_status = FetchStatus::LOADING; tab_associated_data[selected_tab].fetch_type = FetchType::LAZY; tab_associated_data[selected_tab].search_result_text.setString("Loading..."); @@ -1897,18 +1936,33 @@ namespace QuickMedia { for(size_t i = 0; i < tabs.size(); ++i) { TabAssociatedData &associated_data = tab_associated_data[i]; + if(!tabs[i].page->is_ready()) + continue; if(associated_data.fetching_next_page_running && associated_data.next_page_future.ready()) { + const bool body_was_empty = tabs[i].body->items.empty(); BodyItems new_body_items = associated_data.next_page_future.get(); fprintf(stderr, "Finished fetching page %d, num new items: %zu\n", associated_data.fetched_page + 1, new_body_items.size()); + int prev_selected_item = tabs[i].body->get_selected_item(); size_t num_new_messages = new_body_items.size(); if(num_new_messages > 0) { - tabs[i].body->append_items(std::move(new_body_items)); + if(tabs[i].body->attach_side == AttachSide::TOP) + tabs[i].body->append_items(std::move(new_body_items)); + else if(tabs[i].body->attach_side == AttachSide::BOTTOM) + tabs[i].body->prepend_items_reverse(std::move(new_body_items)); associated_data.fetched_page++; } else { associated_data.fetching_next_page_failed = true; } associated_data.fetching_next_page_running = false; + + if(tabs[i].body->attach_side == AttachSide::BOTTOM) { + if(body_was_empty) { + tabs[i].body->select_last_item(); + } else { + tabs[i].body->set_selected_item(prev_selected_item + num_new_messages, true); + } + } } if(associated_data.search_text_updated && associated_data.fetch_status == FetchStatus::NONE && !associated_data.fetching_next_page_running) { @@ -1929,7 +1983,12 @@ namespace QuickMedia { if(!associated_data.search_text_updated) { FetchResult fetch_result = associated_data.fetch_future.get(); tabs[i].body->items = std::move(fetch_result.body_items); - tabs[i].body->select_first_item(); + if(tabs[i].body->attach_side == AttachSide::TOP) { + tabs[i].body->select_first_item(); + } else if(tabs[i].body->attach_side == AttachSide::BOTTOM) { + std::reverse(tabs[i].body->items.begin(), tabs[i].body->items.end()); + tabs[i].body->select_last_item(); + } associated_data.fetched_page = 0; associated_data.fetching_next_page_failed = false; if(fetch_result.result != PluginResult::OK) @@ -1949,6 +2008,10 @@ namespace QuickMedia { FetchResult fetch_result = associated_data.fetch_future.get(); tabs[i].body->items = std::move(fetch_result.body_items); if(tabs[i].search_bar) tabs[i].body->filter_search_fuzzy(tabs[i].search_bar->get_text()); + if(tabs[i].body->attach_side == AttachSide::BOTTOM) { + std::reverse(tabs[i].body->items.begin(), tabs[i].body->items.end()); + tabs[i].body->select_last_item(); + } LazyFetchPage *lazy_fetch_page = static_cast(tabs[i].page.get()); if(fetch_result.result != PluginResult::OK) associated_data.search_result_text.setString("Failed to fetch page!"); @@ -1984,8 +2047,12 @@ namespace QuickMedia { AsyncImageLoader::get_instance().update(); window.display(); - if(!tabs[selected_tab].body->items.empty() && tabs[selected_tab].body->is_last_item_fully_visible()) - on_bottom_reached(); + if(!tabs[selected_tab].body->items.empty()) { + if(tabs[selected_tab].body->attach_side == AttachSide::TOP && tabs[selected_tab].body->is_last_item_fully_visible()) + on_reached_end(); + else if(tabs[selected_tab].body->attach_side == AttachSide::BOTTOM && tabs[selected_tab].body->is_first_item_fully_visible()) + on_reached_end(); + } if(go_to_previous_page) { go_to_previous_page = false; @@ -3373,7 +3440,7 @@ namespace QuickMedia { //thread_body->clamp_selection(); //thread_body->set_page_scroll(0.0f); int prev_sel = thread_body->get_selected_item(); - thread_body->select_last_item(); + thread_body->select_first_item(); thread_body->set_selected_item(prev_sel, false); } else if(event.key.code == sf::Keyboard::BackSpace && !comment_navigation_stack.empty()) { size_t previous_selected = comment_navigation_stack.top(); @@ -5692,6 +5759,8 @@ namespace QuickMedia { // TODO: Maybe set this instead when the mention is visible on the screen? current_room->unread_notification_count = 0; + matrix_chat_page->set_room_as_read(current_room); + Message *read_message = last_visible_timeline_message; if(read_message->replaced_by) read_message = read_message->replaced_by.get(); @@ -5825,7 +5894,8 @@ namespace QuickMedia { exit(exit_code); auto notifications_body = create_body(); - auto matrix_notifications_page = std::make_unique(this, notifications_body.get()); + //notifications_body->attach_side = AttachSide::BOTTOM; + auto matrix_notifications_page = std::make_unique(this, matrix, notifications_body.get()); auto rooms_tags_body = create_body(); auto matrix_rooms_tag_page = std::make_unique(this, rooms_tags_body.get()); @@ -5854,7 +5924,7 @@ namespace QuickMedia { add_body_item_unique_title(room_directory_body->items, "jupiterbroadcasting.com"); auto matrix_room_directory_page = std::make_unique(this, matrix); - MatrixQuickMedia matrix_handler(this, matrix, matrix_rooms_page.get(), matrix_rooms_tag_page.get(), matrix_invites_page.get()); + MatrixQuickMedia matrix_handler(this, matrix, matrix_rooms_page.get(), matrix_rooms_tag_page.get(), matrix_invites_page.get(), matrix_notifications_page.get()); bool sync_cached = false; if(!matrix->start_sync(&matrix_handler, sync_cached)) { show_notification("QuickMedia", "Failed to start sync", Urgency::CRITICAL); diff --git a/src/SearchBar.cpp b/src/SearchBar.cpp index a7e3890..c0a8aad 100644 --- a/src/SearchBar.cpp +++ b/src/SearchBar.cpp @@ -358,4 +358,8 @@ namespace QuickMedia { return ""; return text.getString(); } + + bool SearchBar::is_empty() const { + return show_placeholder; + } } \ No newline at end of file diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index e015ada..7992772 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -348,8 +348,8 @@ namespace QuickMedia { //body_item.reset(); } - MatrixQuickMedia::MatrixQuickMedia(Program *program, Matrix *matrix, MatrixRoomsPage *rooms_page, MatrixRoomTagsPage *room_tags_page, MatrixInvitesPage *invites_page) : - program(program), matrix(matrix), chat_page(nullptr), rooms_page(rooms_page), room_tags_page(room_tags_page), invites_page(invites_page) + MatrixQuickMedia::MatrixQuickMedia(Program *program, Matrix *matrix, MatrixRoomsPage *rooms_page, MatrixRoomTagsPage *room_tags_page, MatrixInvitesPage *invites_page, MatrixNotificationsPage *notifications_page) : + program(program), matrix(matrix), chat_page(nullptr), rooms_page(rooms_page), room_tags_page(room_tags_page), invites_page(invites_page), notifications_page(notifications_page) { rooms_page->matrix_delegate = this; room_tags_page->matrix_delegate = this; @@ -405,7 +405,17 @@ namespace QuickMedia { if(message->notification_mentions_me) { // TODO: What if the message or username begins with "-"? also make the notification image be the avatar of the user if((!is_window_focused || room != current_room) && message->related_event_type != RelatedEventType::EDIT && message->related_event_type != RelatedEventType::REDACTION) { - show_notification("QuickMedia matrix - " + extract_first_line_remove_newline_elipses(matrix->message_get_author_displayname(message.get()), AUTHOR_MAX_LENGTH) + " (" + room->get_name() + ")", message->body); + std::string body = remove_reply_formatting(message->body); + show_notification("QuickMedia matrix - " + extract_first_line_remove_newline_elipses(matrix->message_get_author_displayname(message.get()), AUTHOR_MAX_LENGTH) + " (" + room->get_name() + ")", body); + + MatrixNotification notification; + notification.room = room; + notification.event_id = message->event_id; + notification.sender_user_id = message->user->user_id; + notification.body = std::move(body); + notification.timestamp = message->timestamp; + notification.read = false; + notifications_page->add_notification(std::move(notification)); } } } @@ -433,8 +443,8 @@ namespace QuickMedia { invites_page->remove_body_item_by_room_id(room_id); } - void MatrixQuickMedia::add_unread_notification(RoomData *room, std::string, std::string sender, std::string body) { - show_notification("QuickMedia matrix - " + sender + " (" + room->get_name() + ")", body); + void MatrixQuickMedia::add_unread_notification(MatrixNotification notification) { + show_notification("QuickMedia matrix - " + notification.sender_user_id + " (" + notification.room->get_name() + ")", notification.body); } static UsersByRoom::iterator find_user_data_by_id(UsersByRoom &users_by_room, const MatrixEventUserInfo &user_info) { @@ -481,6 +491,10 @@ namespace QuickMedia { } } + void MatrixQuickMedia::set_room_as_read(RoomData *room) { + notifications_page->set_room_as_read(room); + } + static void sort_room_body_items(std::vector> &room_body_items) { std::sort(room_body_items.begin(), room_body_items.end(), [](const std::shared_ptr &body_item1, const std::shared_ptr &body_item2) { RoomData *room1 = static_cast(body_item1->userdata); @@ -695,6 +709,10 @@ namespace QuickMedia { current_chat_page = chat_page; } + void MatrixRoomsPage::set_room_as_read(RoomData *room) { + matrix_delegate->set_room_as_read(room); + } + void MatrixRoomsPage::clear_data() { body->clear_items(); if(current_chat_page) @@ -966,6 +984,10 @@ namespace QuickMedia { return users_body ? users_body->items.size() : 0; } + void MatrixChatPage::set_room_as_read(RoomData *room) { + rooms_page->set_room_as_read(room); + } + PluginResult MatrixRoomDirectoryPage::submit(const std::string &title, const std::string&, std::vector &result_tabs) { std::string server_name = title; @@ -1011,6 +1033,77 @@ namespace QuickMedia { return PluginResult::OK; } + class NotificationsExtraData : public BodyItemExtra { + public: + RoomData *room; + bool read; + }; + + static std::shared_ptr notification_to_body_item(Body *body, const MatrixNotification ¬ification) { + auto body_item = BodyItem::create(""); + body_item->set_author(notification.room->get_name()); + body_item->set_description(notification.sender_user_id + ":\n" + notification.body); + body_item->set_timestamp(notification.timestamp); + body_item->url = notification.event_id; + + if(!notification.read) { + body_item->set_author_color(sf::Color(255, 100, 100)); + body_item->set_description_color(sf::Color(255, 100, 100)); + } + + body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE; + body_item->thumbnail_size = sf::Vector2i(32, 32); + body_item->thumbnail_url = notification.room->get_avatar_url(); + + auto extra_data = std::make_shared(); + extra_data->room = notification.room; + extra_data->read = notification.read; + body_item->extra = std::move(extra_data); + + body->apply_search_filter_for_item(body_item.get()); + return body_item; + } + + MatrixNotificationsPage::MatrixNotificationsPage(Program *program, Matrix *matrix, Body *notifications_body) : + LazyFetchPage(program), matrix(matrix), notifications_body(notifications_body) {} + + PluginResult MatrixNotificationsPage::get_page(const std::string&, int, BodyItems &result_items) { + return matrix->get_previous_notifications([this, &result_items](const MatrixNotification ¬ification) { + result_items.push_back(notification_to_body_item(notifications_body, notification)); + }); + } + + PluginResult MatrixNotificationsPage::lazy_fetch(BodyItems &result_items) { + matrix->get_cached_notifications([this, &result_items](const MatrixNotification ¬ification) { + result_items.push_back(notification_to_body_item(notifications_body, notification)); + }); + return PluginResult::OK; + } + + bool MatrixNotificationsPage::is_ready() { + return matrix->has_finished_fetching_notifications(); + } + + void MatrixNotificationsPage::add_notification(MatrixNotification notification) { + //int prev_selected_item = notifications_body->get_selected_item(); + //notifications_body->items.push_back(notification_to_body_item(notifications_body, notification)); + //notifications_body->set_selected_item(prev_selected_item - 1); + int prev_selected_item = notifications_body->get_selected_item(); + notifications_body->items.insert(notifications_body->items.begin(), notification_to_body_item(notifications_body, notification)); + notifications_body->set_selected_item(prev_selected_item + 1, false); + } + + void MatrixNotificationsPage::set_room_as_read(RoomData *room) { + for(auto &body_item : notifications_body->items) { + NotificationsExtraData *extra_data = static_cast(body_item->extra.get()); + if(!extra_data->read && extra_data->room == room) { + extra_data->read = true; + body_item->set_author_color(sf::Color::White); + body_item->set_description_color(sf::Color::White); + } + } + } + static std::array sync_fail_error_codes = { "M_FORBIDDEN", "M_UNKNOWN_TOKEN", @@ -1184,24 +1277,17 @@ namespace QuickMedia { if(initial_sync) { notification_thread = std::thread([this]() { - std::vector additional_args = { - { "-H", "Authorization: Bearer " + access_token } - }; - - // TODO: Instead of guessing notification limit with 100, accumulate rooms unread_notifications count and use that as the limit - // (and take into account that notification response may have notifications after call to sync above). - char url[512]; - snprintf(url, sizeof(url), "%s/_matrix/client/r0/notifications?limit=100&only=highlight", homeserver.c_str()); - - rapidjson::Document json_root; - DownloadResult download_result = download_json(json_root, url, std::move(additional_args), true); - if(download_result != DownloadResult::OK || !json_root.IsObject()) { - fprintf(stderr, "Fetching notifications failed!\n"); - return; - } + get_previous_notifications([this](const MatrixNotification ¬ification) { + if(notification.read) + return; + + MatrixDelegate *delegate = this->delegate; + ui_thread_tasks.push([delegate, notification] { + delegate->add_unread_notification(std::move(notification)); + }); + }); - const rapidjson::Value ¬ification_json = GetMember(json_root, "notifications"); - parse_notifications(notification_json); + finished_fetching_notifications = true; { std::vector additional_args = { @@ -1288,10 +1374,12 @@ namespace QuickMedia { delegate = nullptr; sync_failed = false; sync_fail_reason.clear(); - set_next_batch(""); + next_batch.clear(); + next_notifications_token.clear(); invites.clear(); filter_cached.reset(); my_events_transaction_ids.clear(); + finished_fetching_notifications = false; } bool Matrix::is_initial_sync_finished() const { @@ -1307,6 +1395,10 @@ namespace QuickMedia { } } + bool Matrix::has_finished_fetching_notifications() const { + return finished_fetching_notifications; + } + void Matrix::get_room_sync_data(RoomData *room, SyncData &sync_data) { room->acquire_room_lock(); auto &room_messages = room->get_messages_thread_unsafe(); @@ -1356,6 +1448,45 @@ namespace QuickMedia { return PluginResult::OK; } + PluginResult Matrix::get_previous_notifications(std::function callback_func) { + std::vector additional_args = { + { "-H", "Authorization: Bearer " + access_token } + }; + + std::string from = get_next_notifications_token(); + + // TODO: Instead of guessing notification limit with 100, accumulate rooms unread_notifications count and use that as the limit + // (and take into account that notification response may have notifications after call to sync above). + char url[512]; + if(from.empty()) + snprintf(url, sizeof(url), "%s/_matrix/client/r0/notifications?limit=100&only=highlight", homeserver.c_str()); + else + snprintf(url, sizeof(url), "%s/_matrix/client/r0/notifications?limit=100&only=highlight&from=%s", homeserver.c_str(), from.c_str()); + + rapidjson::Document json_root; + DownloadResult download_result = download_json(json_root, url, std::move(additional_args), true); + if(download_result != DownloadResult::OK || !json_root.IsObject()) { + fprintf(stderr, "Fetching notifications failed!\n"); + return PluginResult::ERR; + } + + const rapidjson::Value ¬ification_json = GetMember(json_root, "notifications"); + parse_notifications(notification_json, std::move(callback_func)); + + const rapidjson::Value &next_token_json = GetMember(json_root, "next_token"); + if(next_token_json.IsString()) + set_next_notifications_token(next_token_json.GetString()); + + return PluginResult::OK; + } + + void Matrix::get_cached_notifications(std::function callback_func) { + std::lock_guard lock(notifications_mutex); + for(const auto ¬ification : notifications) { + callback_func(notification); + } + } + PluginResult Matrix::parse_sync_response(const rapidjson::Document &root, bool is_additional_messages_sync, bool initial_sync) { if(!root.IsObject()) return PluginResult::ERR; @@ -1371,22 +1502,28 @@ namespace QuickMedia { return PluginResult::OK; } - PluginResult Matrix::parse_notifications(const rapidjson::Value ¬ifications_json) { + PluginResult Matrix::parse_notifications(const rapidjson::Value ¬ifications_json, std::function callback_func) { if(!notifications_json.IsArray()) return PluginResult::ERR; + std::lock_guard lock(notifications_mutex); for(const rapidjson::Value ¬ification_json : notifications_json.GetArray()) { if(!notification_json.IsObject()) continue; const rapidjson::Value &read_json = GetMember(notification_json, "read"); - if(!read_json.IsBool() || read_json.GetBool()) + if(!read_json.IsBool()) continue; const rapidjson::Value &room_id_json = GetMember(notification_json, "room_id"); if(!room_id_json.IsString()) continue; + time_t timestamp = 0; + const rapidjson::Value &ts_json = GetMember(notification_json, "ts"); + if(ts_json.IsInt64()) + timestamp = ts_json.GetInt64(); + const rapidjson::Value &event_json = GetMember(notification_json, "event"); if(!event_json.IsObject()) continue; @@ -1414,12 +1551,15 @@ namespace QuickMedia { continue; } - std::string event_id(event_id_json.GetString(), event_id_json.GetStringLength()); - std::string sender(sender_json.GetString(), sender_json.GetStringLength()); - std::string body(body_json.GetString(), body_json.GetStringLength()); - ui_thread_tasks.push([this, room, event_id{std::move(event_id)}, sender{std::move(sender)}, body{std::move(body)}] { - delegate->add_unread_notification(room, std::move(event_id), std::move(sender), std::move(body)); - }); + MatrixNotification notification; + notification.room = room; + notification.event_id.assign(event_id_json.GetString(), event_id_json.GetStringLength()); + notification.sender_user_id.assign(sender_json.GetString(), sender_json.GetStringLength()); + notification.body = remove_reply_formatting(body_json.GetString()); + notification.timestamp = timestamp; + notification.read = read_json.GetBool(); + callback_func(notification); + notifications.push_back(std::move(notification)); } return PluginResult::OK; } @@ -3967,6 +4107,16 @@ namespace QuickMedia { return next_batch; } + void Matrix::set_next_notifications_token(std::string new_next_token) { + std::lock_guard lock(next_batch_mutex); + next_notifications_token = std::move(new_next_token); + } + + std::string Matrix::get_next_notifications_token() { + std::lock_guard lock(next_batch_mutex); + return next_notifications_token; + } + void Matrix::clear_sync_cache_for_new_sync() { std::lock_guard room_data_lock(room_data_mutex); std::lock_guard invites_lock(invite_mutex); -- cgit v1.2.3