diff options
-rw-r--r-- | TODO | 5 | ||||
-rw-r--r-- | include/Body.hpp | 4 | ||||
-rw-r--r-- | include/SearchBar.hpp | 3 | ||||
-rw-r--r-- | plugins/Manga.hpp | 4 | ||||
-rw-r--r-- | plugins/Mangadex.hpp | 3 | ||||
-rw-r--r-- | plugins/Manganelo.hpp | 11 | ||||
-rw-r--r-- | plugins/Mangatown.hpp | 5 | ||||
-rw-r--r-- | plugins/Page.hpp | 12 | ||||
-rw-r--r-- | src/Body.cpp | 20 | ||||
-rw-r--r-- | src/QuickMedia.cpp | 257 | ||||
-rw-r--r-- | src/SearchBar.cpp | 25 | ||||
-rw-r--r-- | src/plugins/FileManager.cpp | 1 | ||||
-rw-r--r-- | src/plugins/Fourchan.cpp | 2 | ||||
-rw-r--r-- | src/plugins/Manga.cpp | 6 | ||||
-rw-r--r-- | src/plugins/Mangadex.cpp | 13 | ||||
-rw-r--r-- | src/plugins/Manganelo.cpp | 159 | ||||
-rw-r--r-- | src/plugins/Mangatown.cpp | 20 | ||||
-rw-r--r-- | src/plugins/NyaaSi.cpp | 2 |
18 files changed, 319 insertions, 233 deletions
@@ -90,7 +90,7 @@ Show invites for us in matrix in a seperate tag and show notification when recei Allow choosing which translation/scanlation to use on mangadex. Right now it uses the latest one, which is most likely to be the best. Add file upload to 4chan. Retry download if it fails, at least 3 times (observed to be needed for mangadex images). -Readd autocomplete, but make it better with a proper list. Also readd 4chan login page and manganelo creators page. +Readd autocomplete, but make it better with a proper list. Also readd 4chan login page. Fix logout/login in matrix. Currently it doesn't work because data is cleared while sync is in progress, leading to the first sync sometimes being with previous data... Modify sfml to use GL_COMPRESSED_LUMINANCE and other texture compression modes (in sf::Texture). This reduces memory usage by half. Decrease memory usage even further (mostly in matrix /sync when part of large rooms) by using rapidjson SAX style API to stream json string into SAX style parsing. @@ -114,4 +114,5 @@ Change scroll in body when previous items change size (such as when thumbnail ha Load the replied-to message in the pinned messages tab. Pressing enter on a pinned message should go to the message in the messages tab. Cache pinned messages on disk (messages by event id), but take into consider edits? for example if one message is pinned in the room and then edited multiple times (such as room rules). In that case cache the pinned message to quickly display it and then fetch the latest version from the server and then replace the message with the latest version. -Display file list for nyaa.
\ No newline at end of file +Display file list for nyaa. +Remove reply formatting for NOTICE in matrix as well.
\ No newline at end of file diff --git a/include/Body.hpp b/include/Body.hpp index 5fde04e..f3498c7 100644 --- a/include/Body.hpp +++ b/include/Body.hpp @@ -19,7 +19,7 @@ namespace sf { namespace QuickMedia { class Program; - enum class EmbeddedItemStatus { + enum class FetchStatus { NONE, LOADING, FINISHED_LOADING, @@ -115,7 +115,7 @@ namespace QuickMedia { std::string post_number; void *userdata; // Not managed, should be deallocated by whoever sets this double last_drawn_time; - EmbeddedItemStatus embedded_item_status = EmbeddedItemStatus::NONE; + FetchStatus embedded_item_status = FetchStatus::NONE; std::shared_ptr<BodyItem> embedded_item; // Used by matrix for example to display reply message body. Note: only the first level of embedded items is rendered (not recursive, this is done on purpose) ThumbnailMaskType thumbnail_mask_type = ThumbnailMaskType::NONE; sf::Vector2i thumbnail_size; diff --git a/include/SearchBar.hpp b/include/SearchBar.hpp index 6b7ca6d..eb7a9f2 100644 --- a/include/SearchBar.hpp +++ b/include/SearchBar.hpp @@ -47,9 +47,6 @@ namespace QuickMedia { int text_autosearch_delay; int autocomplete_search_delay; bool caret_visible; - - float padding_vertical = 20.0f; - float background_margin_vertical = 4.0f; private: void clear_autocomplete_if_text_not_substring(); void clear_autocomplete_if_last_char_not_substr(); diff --git a/plugins/Manga.hpp b/plugins/Manga.hpp index 732e208..96a5d53 100644 --- a/plugins/Manga.hpp +++ b/plugins/Manga.hpp @@ -53,5 +53,9 @@ namespace QuickMedia { public: MangaChaptersPage(Program *program, std::string manga_name, std::string manga_url) : TrackablePage(program, std::move(manga_name), std::move(manga_url)) {} TrackResult track(const std::string &str) override; + void on_navigate_to_page() override; + protected: + virtual bool extract_id_from_url(const std::string &url, std::string &manga_id) const = 0; + virtual const char* get_service_name() const = 0; }; }
\ No newline at end of file diff --git a/plugins/Mangadex.hpp b/plugins/Mangadex.hpp index 1b238a2..96bfc50 100644 --- a/plugins/Mangadex.hpp +++ b/plugins/Mangadex.hpp @@ -21,6 +21,9 @@ namespace QuickMedia { public: MangadexChaptersPage(Program *program, std::string manga_name, std::string manga_url) : MangaChaptersPage(program, std::move(manga_name), std::move(manga_url)) {} PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override; + protected: + bool extract_id_from_url(const std::string &url, std::string &manga_id) const override; + const char* get_service_name() const override { return "mangadex"; } }; class MangadexImagesPage : public MangaImagesPage { diff --git a/plugins/Manganelo.hpp b/plugins/Manganelo.hpp index 4fbce5b..e2b602f 100644 --- a/plugins/Manganelo.hpp +++ b/plugins/Manganelo.hpp @@ -12,21 +12,24 @@ namespace QuickMedia { SearchResult search(const std::string &str, BodyItems &result_items) override; PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override; sf::Vector2i get_thumbnail_max_size() override { return sf::Vector2i(101, 141); }; - private: - bool extract_id_from_url(const std::string &url, std::string &manga_id) const; }; class ManganeloChaptersPage : public MangaChaptersPage { public: ManganeloChaptersPage(Program *program, std::string manga_name, std::string manga_url) : MangaChaptersPage(program, std::move(manga_name), std::move(manga_url)) {} PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override; + protected: + bool extract_id_from_url(const std::string &url, std::string &manga_id) const override; + const char* get_service_name() const override { return "manganelo"; } }; - class ManganeloCreatorPage : public Page { + class ManganeloCreatorPage : public LazyFetchPage { public: - ManganeloCreatorPage(Program *program, Creator creator) : Page(program), creator(std::move(creator)) {} + ManganeloCreatorPage(Program *program, Creator creator) : LazyFetchPage(program), creator(std::move(creator)) {} const char* get_title() const override { return creator.name.c_str(); } PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override; + PluginResult lazy_fetch(BodyItems &result_items) override; + sf::Vector2i get_thumbnail_max_size() override { return sf::Vector2i(101, 141); }; private: Creator creator; }; diff --git a/plugins/Mangatown.hpp b/plugins/Mangatown.hpp index 10f2500..66d9d2a 100644 --- a/plugins/Mangatown.hpp +++ b/plugins/Mangatown.hpp @@ -13,14 +13,15 @@ namespace QuickMedia { PluginResult get_page(const std::string &str, int page, BodyItems &result_items) override; PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override; sf::Vector2i get_thumbnail_max_size() override { return sf::Vector2i(101, 141); }; - private: - bool extract_id_from_url(const std::string &url, std::string &manga_id) const; }; class MangatownChaptersPage : public MangaChaptersPage { public: MangatownChaptersPage(Program *program, std::string manga_name, std::string manga_url) : MangaChaptersPage(program, std::move(manga_name), std::move(manga_url)) {} PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override; + protected: + bool extract_id_from_url(const std::string &url, std::string &manga_id) const override; + const char* get_service_name() const override { return "mangatown"; } }; class MangatownImagesPage : public MangaImagesPage { diff --git a/plugins/Page.hpp b/plugins/Page.hpp index f65cd21..b4ea9a6 100644 --- a/plugins/Page.hpp +++ b/plugins/Page.hpp @@ -34,6 +34,11 @@ namespace QuickMedia { // Mutually exclusive with |is_manga_images_page|, |is_image_board_thread_page| and |is_video_page| virtual bool is_single_page() const { return false; } virtual bool is_trackable() const { return false; } + // Mutually exclusive with |is_manga_images_page|, |is_image_board_thread_page| and |is_video_page| + virtual bool is_lazy_fetch_page() const { return false; } + + // This is called both when first navigating to page and when going back to page + virtual void on_navigate_to_page() {}; bool is_tor_enabled(); std::unique_ptr<Body> create_body(); @@ -61,4 +66,11 @@ namespace QuickMedia { const std::string content_title; const std::string content_url; }; + + class LazyFetchPage : public Page { + public: + LazyFetchPage(Program *program) : Page(program) {} + bool is_lazy_fetch_page() const override { return true; } + virtual PluginResult lazy_fetch(BodyItems &result_items) = 0; + }; }
\ No newline at end of file diff --git a/src/Body.cpp b/src/Body.cpp index ee32b08..20e7404 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -86,7 +86,7 @@ namespace QuickMedia { progress_text("", *font, 14), replies_text("", *font, 14), embedded_item_load_text("", *font, 14), - draw_thumbnails(false), + draw_thumbnails(true), wrap_around(false), line_separator_color(sf::Color(32, 37, 43, 255)), body_item_render_callback(nullptr), @@ -574,15 +574,15 @@ namespace QuickMedia { } // TODO: Better message? maybe fallback to the reply message, or message status (such as message redacted) - static const char* embedded_item_status_to_string(EmbeddedItemStatus embedded_item_status) { + static const char* embedded_item_status_to_string(FetchStatus embedded_item_status) { switch(embedded_item_status) { - case EmbeddedItemStatus::NONE: + case FetchStatus::NONE: return ""; - case EmbeddedItemStatus::LOADING: + case FetchStatus::LOADING: return "Loading message..."; - case EmbeddedItemStatus::FINISHED_LOADING: + case FetchStatus::FINISHED_LOADING: return "Finished loading message..."; - case EmbeddedItemStatus::FAILED_TO_LOAD: + case FetchStatus::FAILED_TO_LOAD: return "Failed to load message!"; } return ""; @@ -591,7 +591,7 @@ namespace QuickMedia { void Body::draw_item(sf::RenderWindow &window, BodyItem *item, const sf::Vector2f &pos, const sf::Vector2f &size, const float item_height, const int item_index, const Json::Value &content_progress, bool include_embedded_item) { // TODO: Instead of generating a new hash everytime to access textures, cache the hash of the thumbnail url std::shared_ptr<ThumbnailData> item_thumbnail; - if(draw_thumbnails) { + if(draw_thumbnails && !item->thumbnail_url.empty()) { auto item_thumbnail_it = item_thumbnail_textures.find(item->thumbnail_url); if(item_thumbnail_it == item_thumbnail_textures.end()) { item_thumbnail = std::make_shared<ThumbnailData>(); @@ -620,7 +620,7 @@ namespace QuickMedia { } float text_offset_x = padding_x; - if(draw_thumbnails) { + if(draw_thumbnails && !item->thumbnail_url.empty()) { double elapsed_time_thumbnail = 0.0; if(item_thumbnail->loading_state == LoadingState::APPLIED_TO_TEXTURE) elapsed_time_thumbnail = item_thumbnail->texture_applied_time.getElapsedTime().asSeconds(); //thumbnail_fade_duration_sec @@ -707,7 +707,7 @@ namespace QuickMedia { item_pos.y += item->author_text->getHeight() - 2.0f; } - if(include_embedded_item && item->embedded_item_status != EmbeddedItemStatus::NONE) { + if(include_embedded_item && item->embedded_item_status != FetchStatus::NONE) { float embedded_item_height = item->embedded_item ? get_item_height(item->embedded_item.get(), true, false) : (embedded_item_load_text.getLocalBounds().height + embedded_item_padding_y * 2.0f); const float border_width = 4.0f; sf::RectangleShape border_left(sf::Vector2f(border_width, std::floor(embedded_item_height))); @@ -776,7 +776,7 @@ namespace QuickMedia { if(item->author_text) { item_height += item->author_text->getHeight() - 2.0f; } - if(include_embedded_item && item->embedded_item_status != EmbeddedItemStatus::NONE) { + if(include_embedded_item && item->embedded_item_status != FetchStatus::NONE) { if(item->embedded_item) item_height += (get_item_height(item->embedded_item.get(), load_texture, false) + 4.0f + embedded_item_padding_y * 2.0f); else diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 046ba9d..c9950a0 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -40,7 +40,7 @@ static const sf::Color back_color(21, 25, 30); static const std::string fourchan_google_captcha_api_key = "6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc"; -static const float tab_text_size = 18.0f; +static const float tab_text_size = 16.0f; static const float tab_height = tab_text_size + 10.0f; static const sf::Color tab_selected_color(55, 60, 68); static const float tab_margin_x = 10.0f; @@ -188,6 +188,11 @@ namespace QuickMedia { Page *search_page; }; + template <typename T> + static bool is_future_ready(const std::future<T> &future) { + return future.valid() && future.wait_for(std::chrono::seconds(0)) == std::future_status::ready; + } + static Path get_recommended_filepath(const char *plugin_name) { Path video_history_dir = get_storage_dir().join("recommended"); if(create_directory_recursive(video_history_dir) != 0) { @@ -555,7 +560,6 @@ namespace QuickMedia { if(strcmp(plugin_name, "manganelo") == 0) { auto search_body = create_body(); - search_body->draw_thumbnails = true; tabs.push_back(Tab{std::move(search_body), std::make_unique<ManganeloSearchPage>(this), create_search_bar("Search...", 200)}); auto history_body = create_body(); @@ -563,7 +567,6 @@ namespace QuickMedia { tabs.push_back(Tab{std::move(history_body), std::make_unique<HistoryPage>(this, tabs.front().page.get()), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } else if(strcmp(plugin_name, "mangatown") == 0) { auto search_body = create_body(); - search_body->draw_thumbnails = true; tabs.push_back(Tab{std::move(search_body), std::make_unique<MangatownSearchPage>(this), create_search_bar("Search...", 200)}); auto history_body = create_body(); @@ -571,7 +574,6 @@ namespace QuickMedia { tabs.push_back(Tab{std::move(history_body), std::make_unique<HistoryPage>(this, tabs.front().page.get()), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } else if(strcmp(plugin_name, "mangadex") == 0) { auto search_body = create_body(); - search_body->draw_thumbnails = true; tabs.push_back(Tab{std::move(search_body), std::make_unique<MangadexSearchPage>(this), create_search_bar("Search...", 300)}); auto history_body = create_body(); @@ -598,21 +600,17 @@ namespace QuickMedia { tabs.push_back(Tab{std::move(file_manager_body), std::move(file_manager_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } else if(strcmp(plugin_name, "youtube") == 0) { auto search_body = create_body(); - search_body->draw_thumbnails = true; tabs.push_back(Tab{std::move(search_body), std::make_unique<YoutubeSearchPage>(this), create_search_bar("Search...", 350)}); auto history_body = create_body(); - history_body->draw_thumbnails = true; youtube_get_watch_history(history_body->items); tabs.push_back(Tab{std::move(history_body), std::make_unique<HistoryPage>(this, tabs.front().page.get()), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); auto recommended_body = create_body(); - recommended_body->draw_thumbnails = true; fill_recommended_items_from_json(plugin_name, load_recommended_json(), recommended_body->items); tabs.push_back(Tab{std::move(recommended_body), std::make_unique<RecommendedPage>(this, tabs.front().page.get()), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } else if(strcmp(plugin_name, "pornhub") == 0) { auto search_body = create_body(); - search_body->draw_thumbnails = true; tabs.push_back(Tab{std::move(search_body), std::make_unique<PornhubSearchPage>(this), create_search_bar("Search...", 500)}); } @@ -928,6 +926,7 @@ namespace QuickMedia { for(Tab &tab : tabs) { tab.body->thumbnail_max_size = tab.page->get_thumbnail_max_size(); + tab.page->on_navigate_to_page(); } const Json::Value *json_chapters = &Json::Value::nullSingleton(); @@ -937,15 +936,27 @@ namespace QuickMedia { json_chapters = &chapters_json; } + enum class FetchType { + SEARCH, + LAZY + }; + + struct FetchResult { + BodyItems body_items; + PluginResult result; + }; + struct TabAssociatedData { std::string update_search_text; bool search_text_updated = false; - bool search_running = false; + FetchStatus fetch_status = FetchStatus::NONE; + bool lazy_fetch_finished = false; + FetchType fetch_type; bool typing = false; bool fetching_next_page_running = false; int fetched_page = 0; sf::Text search_result_text; - std::future<BodyItems> search_future; + std::future<FetchResult> fetch_future; std::future<BodyItems> next_page_future; }; @@ -969,7 +980,7 @@ namespace QuickMedia { bool loop_running = true; bool redraw = true; - auto submit_handler = [this, &tabs, &selected_tab, &loop_running, &redraw]() { + auto submit_handler = [this, &json_chapters, &tabs, &selected_tab, &loop_running, &redraw]() { BodyItem *selected_item = tabs[selected_tab].body->get_selected(); if(!selected_item) return; @@ -1039,6 +1050,10 @@ namespace QuickMedia { redraw = true; } else { page_loop(std::move(new_tabs)); + tabs[selected_tab].page->on_navigate_to_page(); + const Json::Value &chapters_json = content_storage_json["chapters"]; + if(chapters_json.isObject()) + json_chapters = &chapters_json; } } else { // TODO: Show the exact cause of error (get error message from curl). @@ -1093,6 +1108,8 @@ namespace QuickMedia { while (window.isOpen() && loop_running) { sf::Int32 frame_time_ms = frame_timer.restart().asMilliseconds(); + Tab ¤t_tab = tabs[selected_tab]; + TabAssociatedData ¤t_tab_associated_data = tab_associated_data[selected_tab]; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) { @@ -1104,10 +1121,10 @@ namespace QuickMedia { window.setView(sf::View(visible_area)); } - if(tabs[selected_tab].search_bar) { + if(current_tab.search_bar) { if(event.type == sf::Event::TextEntered) - tabs[selected_tab].search_bar->onTextEntered(event.text.unicode); - tabs[selected_tab].search_bar->on_event(event); + current_tab.search_bar->onTextEntered(event.text.unicode); + current_tab.search_bar->on_event(event); } if(event.type == sf::Event::Resized || event.type == sf::Event::GainedFocus) @@ -1117,26 +1134,26 @@ namespace QuickMedia { bool hit_bottom = false; switch(event.key.code) { case sf::Keyboard::Down: - hit_bottom = !tabs[selected_tab].body->select_next_item(); + hit_bottom = !current_tab.body->select_next_item(); break; case sf::Keyboard::PageDown: - hit_bottom = !tabs[selected_tab].body->select_next_page(); + hit_bottom = !current_tab.body->select_next_page(); break; case sf::Keyboard::End: - tabs[selected_tab].body->select_last_item(); + current_tab.body->select_last_item(); hit_bottom = true; break; default: hit_bottom = false; break; } - if(hit_bottom && !tab_associated_data[selected_tab].search_running && !tab_associated_data[selected_tab].fetching_next_page_running && tabs[selected_tab].page) { + if(hit_bottom && current_tab_associated_data.fetch_status == FetchStatus::NONE && !current_tab_associated_data.fetching_next_page_running && current_tab.page) { gradient_inc = 0.0; - tab_associated_data[selected_tab].fetching_next_page_running = true; - int next_page = tab_associated_data[selected_tab].fetched_page + 1; - Page *page = tabs[selected_tab].page.get(); - std::string update_search_text = tab_associated_data[selected_tab].update_search_text; - tab_associated_data[selected_tab].next_page_future = std::async(std::launch::async, [update_search_text, next_page, page]() { + current_tab_associated_data.fetching_next_page_running = true; + int next_page = current_tab_associated_data.fetched_page + 1; + Page *page = current_tab.page.get(); + std::string update_search_text = current_tab_associated_data.update_search_text; + current_tab_associated_data.next_page_future = std::async(std::launch::async, [update_search_text, next_page, page]() { BodyItems result_items; if(page->get_page(update_search_text, next_page, result_items) != PluginResult::OK) fprintf(stderr, "Failed to get next page (page %d)\n", next_page); @@ -1144,33 +1161,33 @@ namespace QuickMedia { }); } } else if(event.key.code == sf::Keyboard::Up) { - tabs[selected_tab].body->select_previous_item(); + current_tab.body->select_previous_item(); } else if(event.key.code == sf::Keyboard::PageUp) { - tabs[selected_tab].body->select_previous_page(); + current_tab.body->select_previous_page(); } else if(event.key.code == sf::Keyboard::Home) { - tabs[selected_tab].body->select_first_item(); + current_tab.body->select_first_item(); } else if(event.key.code == sf::Keyboard::Escape) { goto page_end; } else if(event.key.code == sf::Keyboard::Left) { if(selected_tab > 0) { - tabs[selected_tab].body->clear_cache(); + current_tab.body->clear_cache(); --selected_tab; redraw = true; } } else if(event.key.code == sf::Keyboard::Right) { if(selected_tab < (int)tabs.size() - 1) { - tabs[selected_tab].body->clear_cache(); + current_tab.body->clear_cache(); ++selected_tab; redraw = true; } } else if(event.key.code == sf::Keyboard::Tab) { - if(tabs[selected_tab].search_bar) tabs[selected_tab].search_bar->set_to_autocomplete(); + if(current_tab.search_bar) current_tab.search_bar->set_to_autocomplete(); } else if(event.key.code == sf::Keyboard::Enter) { - if(!tabs[selected_tab].search_bar) submit_handler(); + if(!current_tab.search_bar) submit_handler(); } 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()) { - TrackablePage *trackable_page = static_cast<TrackablePage*>(tabs[selected_tab].page.get()); + BodyItem *selected_item = current_tab.body->get_selected(); + if(selected_item && current_tab.page && current_tab.page->is_trackable()) { + TrackablePage *trackable_page = static_cast<TrackablePage*>(current_tab.page.get()); TrackResult track_result = trackable_page->track(selected_item->get_title()); // TODO: Show proper error message when this fails. For example if we are already tracking the manga if(track_result == TrackResult::OK) { @@ -1185,9 +1202,9 @@ namespace QuickMedia { if(redraw) { redraw = false; - if(tabs[selected_tab].search_bar) tabs[selected_tab].search_bar->onWindowResize(window_size); + if(current_tab.search_bar) current_tab.search_bar->onWindowResize(window_size); // 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); + get_body_dimensions(window_size, current_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; @@ -1202,12 +1219,24 @@ namespace QuickMedia { gradient_points[3].position.y = window_size.y; } - if(tabs[selected_tab].search_bar) tabs[selected_tab].search_bar->update(); + if(current_tab.search_bar) current_tab.search_bar->update(); + + if(current_tab.page->is_lazy_fetch_page() && current_tab_associated_data.fetch_status == FetchStatus::NONE && !current_tab_associated_data.lazy_fetch_finished) { + current_tab_associated_data.fetch_status = FetchStatus::LOADING; + current_tab_associated_data.fetch_type = FetchType::LAZY; + current_tab_associated_data.search_result_text.setString("Fetching page..."); + LazyFetchPage *lazy_fetch_page = static_cast<LazyFetchPage*>(current_tab.page.get()); + current_tab_associated_data.fetch_future = std::async(std::launch::async, [lazy_fetch_page]() { + FetchResult fetch_result; + fetch_result.result = lazy_fetch_page->lazy_fetch(fetch_result.body_items); + return fetch_result; + }); + } for(size_t i = 0; i < tabs.size(); ++i) { TabAssociatedData &associated_data = tab_associated_data[i]; - if(associated_data.fetching_next_page_running && associated_data.next_page_future.valid() && associated_data.next_page_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { + if(associated_data.fetching_next_page_running && is_future_ready(associated_data.next_page_future)) { BodyItems new_body_items = associated_data.next_page_future.get(); fprintf(stderr, "Finished fetching page %d, num new messages: %zu\n", associated_data.fetched_page + 1, new_body_items.size()); size_t num_new_messages = new_body_items.size(); @@ -1218,65 +1247,66 @@ namespace QuickMedia { associated_data.fetching_next_page_running = false; } - if(associated_data.search_text_updated && !associated_data.search_running && !associated_data.fetching_next_page_running) { - Page *page = tabs[i].page.get(); + if(associated_data.search_text_updated && associated_data.fetch_status == FetchStatus::NONE && !associated_data.fetching_next_page_running) { std::string update_search_text = associated_data.update_search_text; - associated_data.search_future = std::async(std::launch::async, [update_search_text, page]() { - BodyItems result_items; - if(page->search(update_search_text, result_items) != SearchResult::OK) { - show_notification("QuickMedia", "Search failed!", Urgency::CRITICAL); - } - return result_items; - }); - update_search_text.clear(); + associated_data.update_search_text.clear(); associated_data.search_text_updated = false; - associated_data.search_running = true; + associated_data.fetch_status = FetchStatus::LOADING; + associated_data.fetch_type = FetchType::SEARCH; associated_data.search_result_text.setString("Searching..."); + Page *page = tabs[i].page.get(); + associated_data.fetch_future = std::async(std::launch::async, [update_search_text, page]() { + FetchResult fetch_result; + fetch_result.result = search_result_to_plugin_result(page->search(update_search_text, fetch_result.body_items)); + return fetch_result; + }); } - if(associated_data.search_running && associated_data.search_future.valid() && associated_data.search_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { + if(associated_data.fetch_status == FetchStatus::LOADING && associated_data.fetch_type == FetchType::SEARCH && is_future_ready(associated_data.fetch_future)) { if(!associated_data.search_text_updated) { - BodyItems result_items = associated_data.search_future.get(); - tabs[i].body->items = std::move(result_items); + FetchResult fetch_result = associated_data.fetch_future.get(); + tabs[i].body->items = std::move(fetch_result.body_items); tabs[i].body->select_first_item(); associated_data.fetched_page = 0; - if(tabs[i].body->items.empty()) + if(fetch_result.result != PluginResult::OK) + associated_data.search_result_text.setString("Search failed!"); + else if(tabs[i].body->items.empty()) associated_data.search_result_text.setString("No results found"); else associated_data.search_result_text.setString(""); } else { - associated_data.search_future.get(); + associated_data.fetch_future.get(); } - associated_data.search_running = false; + associated_data.fetch_status = FetchStatus::NONE; } - } - - // if(!autocomplete_text.empty() && !autocomplete_running) { - // autocomplete_future = std::async(std::launch::async, [this, autocomplete_text]() { - // return current_plugin->autocomplete_search(autocomplete_text); - // }); - // autocomplete_text.clear(); - // autocomplete_running = true; - // } - // if(autocomplete_running && autocomplete_future.valid() && autocomplete_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { - // if(tabs[selected_tab].search_bar) tabs[selected_tab].search_bar->set_autocomplete_text(autocomplete_future.get()); - // autocomplete_running = false; - // } + if(associated_data.fetch_status == FetchStatus::LOADING && associated_data.fetch_type == FetchType::LAZY && is_future_ready(associated_data.fetch_future)) { + associated_data.lazy_fetch_finished = true; + FetchResult fetch_result = associated_data.fetch_future.get(); + tabs[i].body->items = std::move(fetch_result.body_items); + if(fetch_result.result != PluginResult::OK) + associated_data.search_result_text.setString("Failed to fetch page!"); + else if(tabs[i].body->items.empty()) + associated_data.search_result_text.setString("No results found"); + else + associated_data.search_result_text.setString(""); + associated_data.fetch_status = FetchStatus::NONE; + } + } window.clear(back_color); - if(tabs[selected_tab].search_bar) tabs[selected_tab].search_bar->draw(window, false); + if(current_tab.search_bar) current_tab.search_bar->draw(window, false); { float shade_extra_height = 0.0f; - if(!tabs[selected_tab].search_bar) + if(!current_tab.search_bar) shade_extra_height = 10.0f; const float width_per_tab = window_size.x / tabs.size(); tab_background.setSize(sf::Vector2f(std::floor(width_per_tab - tab_margin_x * 2.0f), tab_height)); - float tab_vertical_offset = tabs[selected_tab].search_bar ? tabs[selected_tab].search_bar->getBottomWithoutShadow() : 0.0f; - tabs[selected_tab].body->draw(window, body_pos, body_size, *json_chapters); + float tab_vertical_offset = current_tab.search_bar ? current_tab.search_bar->getBottomWithoutShadow() : 0.0f; + current_tab.body->draw(window, body_pos, body_size, *json_chapters); const float tab_y = tab_spacer_height + std::floor(tab_vertical_offset + tab_height * 0.5f - (tab_text_size + 5.0f) * 0.5f) + shade_extra_height; tab_shade.setPosition(0.0f, tab_spacer_height + std::floor(tab_vertical_offset)); @@ -1299,7 +1329,7 @@ namespace QuickMedia { } } - if(tab_associated_data[selected_tab].fetching_next_page_running) { + if(current_tab_associated_data.fetching_next_page_running) { 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 bottom_color = interpolate_colors(back_color, sf::Color(175, 180, 188), progress); @@ -1311,12 +1341,12 @@ namespace QuickMedia { window.draw(gradient_points, 4, sf::Quads); // Note: sf::Quads doesn't work with egl } - if(!tab_associated_data[selected_tab].search_result_text.getString().isEmpty()) { - auto search_result_text_bounds = tab_associated_data[selected_tab].search_result_text.getLocalBounds(); - tab_associated_data[selected_tab].search_result_text.setPosition( + if(!current_tab_associated_data.search_result_text.getString().isEmpty()) { + auto search_result_text_bounds = current_tab_associated_data.search_result_text.getLocalBounds(); + current_tab_associated_data.search_result_text.setPosition( std::floor(body_pos.x + body_size.x * 0.5f - search_result_text_bounds.width * 0.5f), std::floor(body_pos.y + body_size.y * 0.5f - search_result_text_bounds.height * 0.5f)); - window.draw(tab_associated_data[selected_tab].search_result_text); + window.draw(current_tab_associated_data.search_result_text); } window.display(); @@ -1328,8 +1358,8 @@ namespace QuickMedia { for(TabAssociatedData &associated_data : tab_associated_data) { if(associated_data.next_page_future.valid()) associated_data.next_page_future.get(); - if(associated_data.search_future.valid()) - associated_data.search_future.get(); + if(associated_data.fetch_future.valid()) + associated_data.fetch_future.get(); } } @@ -1491,7 +1521,6 @@ namespace QuickMedia { const float related_videos_text_height = related_videos_text.getCharacterSize(); auto related_media_body = create_body(); - related_media_body->draw_thumbnails = true; sf::WindowHandle video_player_window = None; auto on_window_create = [this, &video_player_window](sf::WindowHandle _video_player_window) mutable { @@ -2440,12 +2469,14 @@ namespace QuickMedia { comment_input_shade.setFillColor(sf::Color(33, 38, 44)); sf::Sprite logo_sprite(plugin_logo); + logo_sprite.setScale(0.8f, 0.8f); + sf::Vector2f logo_size(plugin_logo.getSize().x * logo_sprite.getScale().x, plugin_logo.getSize().y * logo_sprite.getScale().y); float prev_chat_height = comment_input.get_height(); float chat_input_height_full = 0.0f; const float logo_padding_x = 15.0f; - const float chat_input_padding_x = 15.0f; - const float chat_input_padding_y = 15.0f; + const float chat_input_padding_x = 10.0f; + const float chat_input_padding_y = 10.0f; sf::Vector2f body_pos; sf::Vector2f body_size; @@ -2641,8 +2672,8 @@ namespace QuickMedia { redraw = false; comment_input.set_max_width(window_size.x); - comment_input.set_max_width(window_size.x - (logo_padding_x + plugin_logo.getSize().x + chat_input_padding_x * 2.0f)); - comment_input.set_position(sf::Vector2f(logo_padding_x + plugin_logo.getSize().x + chat_input_padding_x, chat_input_padding_y)); + comment_input.set_max_width(window_size.x - (logo_padding_x + logo_size.x + chat_input_padding_x + logo_padding_x)); + comment_input.set_position(sf::Vector2f(std::floor(logo_padding_x + logo_size.x + chat_input_padding_x), chat_input_padding_y)); float body_padding_horizontal = 25.0f; float body_padding_vertical = 5.0f; @@ -2658,7 +2689,7 @@ namespace QuickMedia { body_pos = sf::Vector2f(body_padding_horizontal, comment_input_shade.getSize().y + body_padding_vertical); body_size = sf::Vector2f(body_width, window_size.y - comment_input_shade.getSize().y - body_padding_vertical); - logo_sprite.setPosition(logo_padding_x, comment_input_shade.getSize().y * 0.5f - plugin_logo.getSize().y * 0.5f); + logo_sprite.setPosition(logo_padding_x, std::floor(comment_input_shade.getSize().y * 0.5f - logo_size.y * 0.5f)); } //comment_input.update(); @@ -2702,7 +2733,7 @@ namespace QuickMedia { } else if(navigation_stage == NavigationStage::VIEWING_ATTACHED_IMAGE) { // TODO: Use image instead of data with string. texture->loadFromMemory creates a temporary image anyways that parses the string. std::string image_data; - if(downloading_image && load_image_future.valid() && load_image_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { + if(downloading_image && is_future_ready(load_image_future)) { downloading_image = false; image_data = load_image_future.get(); @@ -2932,16 +2963,9 @@ namespace QuickMedia { return result_items; } - enum class PinnedEventStatus { - NONE, - LOADING, - FINISHED_LOADING, - FAILED_TO_LOAD - }; - struct PinnedEventData { std::string event_id; - PinnedEventStatus status = PinnedEventStatus::NONE; + FetchStatus status = FetchStatus::NONE; }; void Program::chat_page() { @@ -2953,7 +2977,6 @@ namespace QuickMedia { ChatTab pinned_tab; pinned_tab.body = std::make_unique<Body>(this, font.get(), bold_font.get(), cjk_font.get(), loading_icon); - pinned_tab.body->draw_thumbnails = true; pinned_tab.body->thumbnail_max_size = CHAT_MESSAGE_THUMBNAIL_MAX_SIZE; pinned_tab.body->thumbnail_mask_shader = &circle_mask_shader; //pinned_tab.body->line_separator_color = sf::Color::Transparent; @@ -2962,7 +2985,6 @@ namespace QuickMedia { ChatTab messages_tab; messages_tab.body = std::make_unique<Body>(this, font.get(), bold_font.get(), cjk_font.get(), loading_icon); - messages_tab.body->draw_thumbnails = true; messages_tab.body->thumbnail_max_size = CHAT_MESSAGE_THUMBNAIL_MAX_SIZE; messages_tab.body->thumbnail_mask_shader = &circle_mask_shader; //messages_tab.body->line_separator_color = sf::Color::Transparent; @@ -2971,7 +2993,6 @@ namespace QuickMedia { ChatTab rooms_tab; rooms_tab.body = std::make_unique<Body>(this, font.get(), bold_font.get(), cjk_font.get(), loading_icon); - rooms_tab.body->draw_thumbnails = true; //rooms_tab.body->line_separator_color = sf::Color::Transparent; rooms_tab.body->thumbnail_mask_shader = &circle_mask_shader; rooms_tab.text = sf::Text("Rooms", *font, tab_text_size); @@ -3106,6 +3127,8 @@ namespace QuickMedia { sf::Text replying_to_text("Replying to:", *font, 18); sf::Sprite logo_sprite(plugin_logo); + logo_sprite.setScale(0.8f, 0.8f); + sf::Vector2f logo_size(plugin_logo.getSize().x * logo_sprite.getScale().x, plugin_logo.getSize().y * logo_sprite.getScale().y); sf::Text room_name_text("", *bold_font, 18); const float room_name_text_height = 20.0f; @@ -3141,7 +3164,7 @@ namespace QuickMedia { auto set_body_as_deleted = [](Message *message, BodyItem *body_item) { body_item->embedded_item = nullptr; - body_item->embedded_item_status = EmbeddedItemStatus::NONE; + body_item->embedded_item_status = FetchStatus::NONE; body_item->thumbnail_url = message->user->avatar_url; body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE; body_item->set_description_color(sf::Color::White); @@ -3196,7 +3219,7 @@ namespace QuickMedia { body->set_description("Loading message..."); PinnedEventData *event_data = new PinnedEventData(); event_data->event_id = event; - event_data->status = PinnedEventStatus::NONE; + event_data->status = FetchStatus::NONE; body->userdata = event_data; tabs[PINNED_TAB_INDEX].body->items.push_back(std::move(body)); } @@ -3346,14 +3369,14 @@ namespace QuickMedia { return; PinnedEventData *event_data = static_cast<PinnedEventData*>(body_item->userdata); - if(!event_data || event_data->status != PinnedEventStatus::NONE) + if(!event_data || event_data->status != FetchStatus::NONE) return; // Check if we already have the referenced message as a body item in the messages list, so we dont create a new one auto related_body_item = find_body_item_by_event_id(tabs[MESSAGES_TAB_INDEX].body->items.data(), tabs[MESSAGES_TAB_INDEX].body->items.size(), event_data->event_id); if(related_body_item) { *body_item = *related_body_item; - event_data->status = PinnedEventStatus::FINISHED_LOADING; + event_data->status = FetchStatus::FINISHED_LOADING; body_item->userdata = event_data; return; } @@ -3362,7 +3385,7 @@ namespace QuickMedia { std::string message_event_id = event_data->event_id; fetch_future_room = current_room; fetch_body_item = body_item; - body_item->embedded_item_status = EmbeddedItemStatus::LOADING; + body_item->embedded_item_status = FetchStatus::LOADING; fetch_message_tab = PINNED_TAB_INDEX; // TODO: Check if the message is already cached before calling async? is this needed? is async creation expensive? fetch_message_future = std::async(std::launch::async, [this, &fetch_future_room, message_event_id]() { @@ -3378,14 +3401,14 @@ namespace QuickMedia { Message *message = static_cast<Message*>(body_item->userdata); assert(message); - if(message->related_event_id.empty() || body_item->embedded_item_status != EmbeddedItemStatus::NONE) + if(message->related_event_id.empty() || body_item->embedded_item_status != FetchStatus::NONE) return; // Check if we already have the referenced message as a body item, so we dont create a new one auto related_body_item = find_body_item_by_event_id(tabs[MESSAGES_TAB_INDEX].body->items.data(), tabs[MESSAGES_TAB_INDEX].body->items.size(), message->related_event_id); if(related_body_item) { body_item->embedded_item = related_body_item; - body_item->embedded_item_status = EmbeddedItemStatus::FINISHED_LOADING; + body_item->embedded_item_status = FetchStatus::FINISHED_LOADING; return; } @@ -3393,7 +3416,7 @@ namespace QuickMedia { std::string message_event_id = message->related_event_id; fetch_future_room = current_room; fetch_body_item = body_item; - body_item->embedded_item_status = EmbeddedItemStatus::LOADING; + body_item->embedded_item_status = FetchStatus::LOADING; fetch_message_tab = MESSAGES_TAB_INDEX; // TODO: Check if the message is already cached before calling async? is this needed? is async creation expensive? fetch_message_future = std::async(std::launch::async, [this, &fetch_future_room, message_event_id]() { @@ -3442,8 +3465,8 @@ namespace QuickMedia { float prev_chat_height = chat_input.get_height(); float chat_input_height_full = 0.0f; const float logo_padding_x = 15.0f; - const float chat_input_padding_x = 15.0f; - const float chat_input_padding_y = 15.0f; + const float chat_input_padding_x = 10.0f; + const float chat_input_padding_y = 10.0f; Body url_selection_body(this, font.get(), bold_font.get(), cjk_font.get(), loading_icon); @@ -3902,8 +3925,8 @@ namespace QuickMedia { tab_shade_height = tab_spacer_height + std::floor(tab_vertical_offset) + tab_height + room_name_padding_y + padding_bottom; - chat_input.set_max_width(window_size.x - (logo_padding_x + plugin_logo.getSize().x + chat_input_padding_x * 2.0f)); - chat_input.set_position(sf::Vector2f(logo_padding_x + plugin_logo.getSize().x + chat_input_padding_x, window_size.y - chat_height - chat_input_padding_y)); + chat_input.set_max_width(window_size.x - (logo_padding_x + logo_size.x + chat_input_padding_x + logo_padding_x)); + chat_input.set_position(sf::Vector2f(std::floor(logo_padding_x + logo_size.x + chat_input_padding_x), window_size.y - chat_height - chat_input_padding_y)); float body_padding_horizontal = 25.0f; float body_padding_vertical = 5.0f; @@ -3922,7 +3945,7 @@ namespace QuickMedia { more_messages_below_rect.setSize(sf::Vector2f(window_size.x, gradient_height)); more_messages_below_rect.setPosition(0.0f, std::floor(window_size.y - chat_input_shade.getSize().y - gradient_height)); - logo_sprite.setPosition(logo_padding_x, window_size.y - chat_input_shade.getSize().y * 0.5f - plugin_logo.getSize().y * 0.5f); + logo_sprite.setPosition(logo_padding_x, std::floor(window_size.y - chat_input_shade.getSize().y * 0.5f - logo_size.y * 0.5f)); } room_search_bar.update(); @@ -3945,7 +3968,7 @@ namespace QuickMedia { }); } - if(sync_future.valid() && sync_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { + if(is_future_ready(sync_future)) { SyncFutureResult sync_result = sync_future.get(); add_new_rooms(sync_result.rooms); @@ -3962,13 +3985,13 @@ namespace QuickMedia { synced = true; } - if(set_read_marker_future.valid() && set_read_marker_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { + if(is_future_ready(set_read_marker_future)) { set_read_marker_future.get(); read_marker_timer.restart(); setting_read_marker = false; } - if(fetching_previous_messages_running && previous_messages_future.valid() && previous_messages_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { + if(fetching_previous_messages_running && is_future_ready(previous_messages_future)) { Messages new_messages = previous_messages_future.get(); fprintf(stderr, "Finished fetching older messages, num new messages: %zu\n", new_messages.size()); // Ignore finished fetch of messages if it happened in another room. When we navigate back to the room we will get the messages again @@ -3989,7 +4012,7 @@ namespace QuickMedia { fetching_previous_messages_running = false; } - if(fetching_message_running && fetch_message_future.valid() && fetch_message_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { + if(fetching_message_running && is_future_ready(fetch_message_future)) { std::shared_ptr<Message> message = fetch_message_future.get(); fprintf(stderr, "Finished fetching message: %s\n", message ? message->event_id.c_str() : "(null)"); // Ignore finished fetch of messages if it happened in another room. When we navigate back to the room we will get the messages again @@ -3998,18 +4021,18 @@ namespace QuickMedia { PinnedEventData *event_data = static_cast<PinnedEventData*>(fetch_body_item->userdata); if(message) { *fetch_body_item = *message_to_body_item(message.get(), matrix->get_me(current_room).get()); - event_data->status = PinnedEventStatus::FINISHED_LOADING; + event_data->status = FetchStatus::FINISHED_LOADING; fetch_body_item->userdata = event_data; } else { fetch_body_item->set_description("Failed to load message!"); - event_data->status = PinnedEventStatus::FAILED_TO_LOAD; + event_data->status = FetchStatus::FAILED_TO_LOAD; } } else if(fetch_message_tab == MESSAGES_TAB_INDEX) { if(message) { fetch_body_item->embedded_item = message_to_body_item(message.get(), matrix->get_me(current_room).get()); - fetch_body_item->embedded_item_status = EmbeddedItemStatus::FINISHED_LOADING; + fetch_body_item->embedded_item_status = FetchStatus::FINISHED_LOADING; } else { - fetch_body_item->embedded_item_status = EmbeddedItemStatus::FAILED_TO_LOAD; + fetch_body_item->embedded_item_status = FetchStatus::FAILED_TO_LOAD; } } } diff --git a/src/SearchBar.cpp b/src/SearchBar.cpp index 382b06a..6895dcb 100644 --- a/src/SearchBar.cpp +++ b/src/SearchBar.cpp @@ -8,10 +8,13 @@ // TODO: Use a seperate placeholder sf::Text instead of switching the text to placeholder text.... -const sf::Color text_placeholder_color(255, 255, 255, 100); -const sf::Color front_color(55, 60, 68); -const float background_margin_horizontal = 15.0f; -const float PADDING_HORIZONTAL = 25.0f; +static const sf::Color text_placeholder_color(255, 255, 255, 100); +static const sf::Color front_color(55, 60, 68); +static const float background_margin_horizontal = 15.0f; +static const float PADDING_HORIZONTAL = 25.0f; +static const float padding_top = 10.0f; +static const float padding_bottom = 15.0f; +static const float background_margin_vertical = 4.0f; namespace QuickMedia { SearchBar::SearchBar(sf::Font &font, sf::Texture *plugin_logo, const std::string &placeholder, bool input_masked) : @@ -22,8 +25,8 @@ namespace QuickMedia { text_autosearch_delay(0), autocomplete_search_delay(0), caret_visible(true), - text(placeholder, font, 18), - autocomplete_text("", font, 18), + text(placeholder, font, 16), + autocomplete_text("", font, 16), background(sf::Vector2f(1.0f, 1.0f), 10.0f, 10), placeholder_str(placeholder), show_placeholder(true), @@ -127,20 +130,20 @@ namespace QuickMedia { sf::Vector2f texture_size_f(texture_size.x, texture_size.y); sf::Vector2f new_size = wrap_to_size(texture_size_f, sf::Vector2f(200.0f, one_line_height)); plugin_logo_sprite.setScale(get_ratio(texture_size_f, new_size)); - plugin_logo_sprite.setPosition(25.0f, padding_vertical + vertical_pos); + plugin_logo_sprite.setPosition(25.0f, padding_top + vertical_pos); offset_x = 25.0f + new_size.x + 25.0f; } const float width = std::floor(window_size.x - offset_x - padding_horizontal); background.setSize(sf::Vector2f(width, rect_height)); - shade.setSize(sf::Vector2f(window_size.x, padding_vertical + rect_height + padding_vertical)); + shade.setSize(sf::Vector2f(window_size.x, padding_top + rect_height + padding_bottom)); caret.setSize(sf::Vector2f(2.0f, text.getCharacterSize() + 2.0f)); background_shadow.setSize(sf::Vector2f(window_size.x, 5.0f)); - background.setPosition(offset_x, padding_vertical + vertical_pos); + background.setPosition(offset_x, padding_top + vertical_pos); shade.setPosition(0.0f, vertical_pos); background_shadow.setPosition(0.0f, std::floor(shade.getSize().y + vertical_pos)); - sf::Vector2f font_position(std::floor(offset_x + background_margin_horizontal), std::floor(padding_vertical + background_margin_vertical + vertical_pos)); + sf::Vector2f font_position(std::floor(offset_x + background_margin_horizontal), std::floor(padding_top + background_margin_vertical + vertical_pos)); autocomplete_text.setPosition(font_position); text.setPosition(font_position); } @@ -302,7 +305,7 @@ namespace QuickMedia { float SearchBar::getBottomWithoutShadow() const { float font_height = text.getCharacterSize() + 7.0f; - return std::floor(font_height + background_margin_vertical * 2.0f) + padding_vertical + padding_vertical; + return std::floor(font_height + background_margin_vertical * 2.0f) + padding_top + padding_bottom; } std::string SearchBar::get_text() const { diff --git a/src/plugins/FileManager.cpp b/src/plugins/FileManager.cpp index 5fac79c..f65486e 100644 --- a/src/plugins/FileManager.cpp +++ b/src/plugins/FileManager.cpp @@ -48,7 +48,6 @@ namespace QuickMedia { auto body = create_body(); body->items = std::move(result_items); - body->draw_thumbnails = true; result_tabs.push_back(Tab{std::move(body), nullptr, nullptr}); return PluginResult::OK; } diff --git a/src/plugins/Fourchan.cpp b/src/plugins/Fourchan.cpp index 7f8008c..d042a2a 100644 --- a/src/plugins/Fourchan.cpp +++ b/src/plugins/Fourchan.cpp @@ -267,7 +267,6 @@ namespace QuickMedia { auto body = create_body(); body->items = std::move(result_items); - body->draw_thumbnails = true; result_tabs.push_back(Tab{std::move(body), std::make_unique<FourchanThreadListPage>(program, title, url), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); return PluginResult::OK; } @@ -459,7 +458,6 @@ namespace QuickMedia { auto body = create_body(); body->items = std::move(result_items); - body->draw_thumbnails = true; result_tabs.push_back(Tab{std::move(body), std::make_unique<FourchanThreadPage>(program, board_id, url, std::move(cached_media_urls)), nullptr}); return PluginResult::OK; } diff --git a/src/plugins/Manga.cpp b/src/plugins/Manga.cpp index 70a1664..323794c 100644 --- a/src/plugins/Manga.cpp +++ b/src/plugins/Manga.cpp @@ -9,4 +9,10 @@ namespace QuickMedia { else return TrackResult::ERR; } + + void MangaChaptersPage::on_navigate_to_page() { + std::string manga_id; + if(extract_id_from_url(content_url, manga_id)) + load_manga_content_storage(get_service_name(), content_title, manga_id); + } }
\ No newline at end of file diff --git a/src/plugins/Mangadex.cpp b/src/plugins/Mangadex.cpp index 3574914..e798b60 100644 --- a/src/plugins/Mangadex.cpp +++ b/src/plugins/Mangadex.cpp @@ -112,8 +112,7 @@ namespace QuickMedia { } PluginResult MangadexSearchPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) { - std::string manga_id = title_url_extract_manga_id(url); - std::string request_url = "https://mangadex.org/api/?id=" + manga_id + "&type=manga"; + std::string request_url = "https://mangadex.org/api/?id=" + title_url_extract_manga_id(url) + "&type=manga"; rapidjson::Document json_root; DownloadResult result = download_to_json(request_url, json_root, {}, is_tor_enabled(), true); @@ -169,10 +168,7 @@ namespace QuickMedia { } result_tabs.push_back(Tab{std::move(body), std::make_unique<MangadexChaptersPage>(program, title, url), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); - - if(load_manga_content_storage("mangadex", title, manga_id)) - return PluginResult::OK; - return PluginResult::ERR; + return PluginResult::OK; } bool MangadexSearchPage::get_rememberme_token(std::string &rememberme_token_output) { @@ -212,6 +208,11 @@ namespace QuickMedia { return PluginResult::OK; } + bool MangadexChaptersPage::extract_id_from_url(const std::string &url, std::string &manga_id) const { + manga_id = title_url_extract_manga_id(url); + return true; + } + ImageResult MangadexImagesPage::get_number_of_images(int &num_images) { num_images = 0; ImageResult image_result = get_image_urls_for_chapter(url); diff --git a/src/plugins/Manganelo.cpp b/src/plugins/Manganelo.cpp index b260dea..7f0a2f9 100644 --- a/src/plugins/Manganelo.cpp +++ b/src/plugins/Manganelo.cpp @@ -25,49 +25,12 @@ namespace QuickMedia { return true; } - SearchResult ManganeloSearchPage::search(const std::string &str, BodyItems &result_items) { - std::string url = "https://manganelo.com/getstorysearchjson"; - std::string search_term = "searchword="; - search_term += url_param_encode(str); - CommandArg data_arg = { "--data", std::move(search_term) }; - - Json::Value json_root; - DownloadResult result = download_json(json_root, url, {data_arg}, true); - if(result != DownloadResult::OK) return download_result_to_search_result(result); - - if(json_root.isNull()) - return SearchResult::OK; - - if(!json_root.isArray()) - return SearchResult::ERR; - - for(const Json::Value &child : json_root) { - if(!child.isObject()) - continue; - - Json::Value name = child.get("name", ""); - Json::Value nameunsigned = child.get("nameunsigned", ""); - if(name.isString() && name.asCString()[0] != '\0' && nameunsigned.isString() && nameunsigned.asCString()[0] != '\0') { - std::string name_str = name.asString(); - while(remove_html_span(name_str)) {} - auto item = BodyItem::create(strip(name_str)); - item->url = "https://manganelo.com/manga/" + url_param_encode(nameunsigned.asString()); - Json::Value image = child.get("image", ""); - if(image.isString() && image.asCString()[0] != '\0') - item->thumbnail_url = image.asString(); - result_items.push_back(std::move(item)); - } - } - - return SearchResult::OK; - } - - PluginResult ManganeloSearchPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) { + static PluginResult submit_manga(Page *page, const std::string &title, const std::string &url, std::vector<Tab> &result_tabs, bool use_tor) { BodyItems chapters_items; std::vector<Creator> creators; std::string website_data; - if(download_to_string(url, website_data, {}, is_tor_enabled(), true) != DownloadResult::OK) + if(download_to_string(url, website_data, {}, use_tor, true) != DownloadResult::OK) return PluginResult::NET_ERR; QuickMediaHtmlSearch html_search; @@ -105,23 +68,64 @@ namespace QuickMedia { if(result != 0) return PluginResult::ERR; - auto chapters_body = create_body(); + auto chapters_body = page->create_body(); chapters_body->items = std::move(chapters_items); - result_tabs.push_back(Tab{std::move(chapters_body), std::make_unique<ManganeloChaptersPage>(program, title, url), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + result_tabs.push_back(Tab{std::move(chapters_body), std::make_unique<ManganeloChaptersPage>(page->program, title, url), page->create_search_bar("Search...", SEARCH_DELAY_FILTER)}); for(Creator &creator : creators) { - result_tabs.push_back(Tab{create_body(), std::make_unique<ManganeloCreatorPage>(program, std::move(creator)), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + result_tabs.push_back(Tab{page->create_body(), std::make_unique<ManganeloCreatorPage>(page->program, std::move(creator)), page->create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } - std::string manga_id; - if(extract_id_from_url(url, manga_id)) { - if(load_manga_content_storage("manganelo", title, manga_id)) - return PluginResult::OK; + return PluginResult::OK; + } + + SearchResult ManganeloSearchPage::search(const std::string &str, BodyItems &result_items) { + std::string url = "https://manganelo.com/getstorysearchjson"; + std::string search_term = "searchword="; + search_term += url_param_encode(str); + CommandArg data_arg = { "--data", std::move(search_term) }; + + Json::Value json_root; + DownloadResult result = download_json(json_root, url, {data_arg}, true); + if(result != DownloadResult::OK) return download_result_to_search_result(result); + + if(json_root.isNull()) + return SearchResult::OK; + + if(!json_root.isArray()) + return SearchResult::ERR; + + for(const Json::Value &child : json_root) { + if(!child.isObject()) + continue; + + Json::Value name = child.get("name", ""); + Json::Value nameunsigned = child.get("nameunsigned", ""); + if(name.isString() && name.asCString()[0] != '\0' && nameunsigned.isString() && nameunsigned.asCString()[0] != '\0') { + std::string name_str = name.asString(); + while(remove_html_span(name_str)) {} + auto item = BodyItem::create(strip(name_str)); + item->url = "https://manganelo.com/manga/" + url_param_encode(nameunsigned.asString()); + Json::Value image = child.get("image", ""); + if(image.isString() && image.asCString()[0] != '\0') + item->thumbnail_url = image.asString(); + result_items.push_back(std::move(item)); + } } - return PluginResult::ERR; + + return SearchResult::OK; + } + + PluginResult ManganeloSearchPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) { + return submit_manga(this, title, url, result_tabs, is_tor_enabled()); + } + + PluginResult ManganeloChaptersPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) { + result_tabs.push_back(Tab{create_body(), std::make_unique<ManganeloImagesPage>(program, content_title, title, url), nullptr}); + return PluginResult::OK; } - - bool ManganeloSearchPage::extract_id_from_url(const std::string &url, std::string &manga_id) const { + + bool ManganeloChaptersPage::extract_id_from_url(const std::string &url, std::string &manga_id) const { bool manganelo_website = false; if(url.find("mangakakalot") != std::string::npos || url.find("manganelo") != std::string::npos) manganelo_website = true; @@ -154,17 +158,56 @@ namespace QuickMedia { } } - PluginResult ManganeloChaptersPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) { - result_tabs.push_back(Tab{create_body(), std::make_unique<ManganeloImagesPage>(program, content_title, title, url), nullptr}); - return PluginResult::OK; + PluginResult ManganeloCreatorPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) { + return submit_manga(this, title, url, result_tabs, is_tor_enabled()); } - PluginResult ManganeloCreatorPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) { - (void)title; - (void)url; - (void)result_tabs; - // TODO: Implement - return PluginResult::ERR; + PluginResult ManganeloCreatorPage::lazy_fetch(BodyItems &result_items) { + std::string website_data; + if(download_to_string(creator.url, website_data, {}, is_tor_enabled(), true) != DownloadResult::OK) + return PluginResult::NET_ERR; + + QuickMediaHtmlSearch html_search; + int result = quickmedia_html_search_init(&html_search, website_data.c_str()); + if(result != 0) + goto cleanup; + + result = quickmedia_html_find_nodes_xpath(&html_search, "//div[class='search-story-item']//a[class='item-img']", + [](QuickMediaHtmlNode *node, void *userdata) { + auto *item_data = (BodyItems*)userdata; + const char *href = quickmedia_html_node_get_attribute_value(node, "href"); + const char *title = quickmedia_html_node_get_attribute_value(node, "title"); + if(href && title && strstr(href, "/manga/")) { + auto body_item = BodyItem::create(title); + body_item->url = href; + item_data->push_back(std::move(body_item)); + } + }, &result_items); + + if(result != 0) + goto cleanup; + + BodyItemImageContext body_item_image_context; + body_item_image_context.body_items = &result_items; + body_item_image_context.index = 0; + + result = quickmedia_html_find_nodes_xpath(&html_search, "//div[class='search-story-item']//a[class='item-img']//img", + [](QuickMediaHtmlNode *node, void *userdata) { + auto *item_data = (BodyItemImageContext*)userdata; + const char *src = quickmedia_html_node_get_attribute_value(node, "src"); + if(src && item_data->index < item_data->body_items->size()) { + (*item_data->body_items)[item_data->index]->thumbnail_url = src; + item_data->index++; + } + }, &body_item_image_context); + + cleanup: + quickmedia_html_search_deinit(&html_search); + if(result != 0) { + result_items.clear(); + return PluginResult::ERR; + } + return PluginResult::OK; } ImageResult ManganeloImagesPage::get_number_of_images(int &num_images) { diff --git a/src/plugins/Mangatown.cpp b/src/plugins/Mangatown.cpp index d9013f7..89bf447 100644 --- a/src/plugins/Mangatown.cpp +++ b/src/plugins/Mangatown.cpp @@ -106,16 +106,15 @@ namespace QuickMedia { auto body = create_body(); body->items = std::move(chapters_items); result_tabs.push_back(Tab{std::move(body), std::make_unique<MangatownChaptersPage>(program, title, url), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + return PluginResult::OK; + } - std::string manga_id; - if(extract_id_from_url(url, manga_id)) { - if(load_manga_content_storage("mangatown", title, manga_id)) - return PluginResult::OK; - } - return PluginResult::ERR; + PluginResult MangatownChaptersPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) { + result_tabs.push_back(Tab{create_body(), std::make_unique<MangatownImagesPage>(program, content_title, title, url), nullptr}); + return PluginResult::OK; } - - bool MangatownSearchPage::extract_id_from_url(const std::string &url, std::string &manga_id) const { + + bool MangatownChaptersPage::extract_id_from_url(const std::string &url, std::string &manga_id) const { size_t start_index = url.find("/manga/"); if(start_index == std::string::npos) return false; @@ -131,11 +130,6 @@ namespace QuickMedia { return true; } - PluginResult MangatownChaptersPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) { - result_tabs.push_back(Tab{create_body(), std::make_unique<MangatownImagesPage>(program, content_title, title, url), nullptr}); - return PluginResult::OK; - } - ImageResult MangatownImagesPage::get_number_of_images(int &num_images) { num_images = 0; ImageResult image_result = get_image_urls_for_chapter(url); diff --git a/src/plugins/NyaaSi.cpp b/src/plugins/NyaaSi.cpp index fa4e94f..b6a6488 100644 --- a/src/plugins/NyaaSi.cpp +++ b/src/plugins/NyaaSi.cpp @@ -184,7 +184,6 @@ namespace QuickMedia { auto body = create_body(); body->items = std::move(result_items); - body->draw_thumbnails = true; result_tabs.push_back(Tab{std::move(body), std::make_unique<NyaaSiSearchPage>(program, strip(title), url), create_search_bar("Search...", 200)}); return PluginResult::OK; } @@ -372,7 +371,6 @@ namespace QuickMedia { auto body = create_body(); body->items = std::move(result_items); - body->draw_thumbnails = true; result_tabs.push_back(Tab{std::move(body), std::make_unique<NyaaSiTorrentPage>(program), nullptr}); return PluginResult::OK; } |