aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md3
-rw-r--r--include/Body.hpp3
-rw-r--r--include/QuickMedia.hpp2
-rw-r--r--include/SearchBar.hpp3
-rw-r--r--include/Storage.hpp6
-rw-r--r--project.conf3
-rw-r--r--src/QuickMedia.cpp150
-rw-r--r--src/SearchBar.cpp9
-rw-r--r--src/Storage.cpp9
9 files changed, 156 insertions, 32 deletions
diff --git a/README.md b/README.md
index b8b0970..d19b120 100644
--- a/README.md
+++ b/README.md
@@ -47,4 +47,5 @@ The search should wait until there are search results before clearing the search
Somehow deal with youtube banning ip when searching too often.\
Optimize shadow rendering for items (Right now they fill too much space that is behind items). It should also be a blurry shadow.\
When continuing to read manga from a different page from the first and there is no cache for the chapter,
-then start downloading from the current page instead of page 1.
+then start downloading from the current page instead of page 1.\
+Show progress of manga in the history tab (current chapter out of total chapters).
diff --git a/include/Body.hpp b/include/Body.hpp
index c394519..fd1e438 100644
--- a/include/Body.hpp
+++ b/include/Body.hpp
@@ -44,7 +44,8 @@ namespace QuickMedia {
static bool string_find_case_insensitive(const std::string &str, const std::string &substr);
// TODO: Make this actually fuzzy... Right now it's just a case insensitive string find.
- // TODO: Highlight the part of the text that matches the search
+ // TODO: Highlight the part of the text that matches the search.
+ // TODO: Ignore dot, whitespace and special characters
void filter_search_fuzzy(const std::string &text);
sf::Text title_text;
diff --git a/include/QuickMedia.hpp b/include/QuickMedia.hpp
index 6906036..7bf8231 100644
--- a/include/QuickMedia.hpp
+++ b/include/QuickMedia.hpp
@@ -24,7 +24,7 @@ namespace QuickMedia {
Plugin* get_current_plugin() { return current_plugin; }
private:
- void base_event_handler(sf::Event &event, Page previous_page);
+ void base_event_handler(sf::Event &event, Page previous_page, bool handle_key_press = true);
void search_suggestion_page();
void search_result_page();
void video_content_page();
diff --git a/include/SearchBar.hpp b/include/SearchBar.hpp
index 8ac8766..88eed19 100644
--- a/include/SearchBar.hpp
+++ b/include/SearchBar.hpp
@@ -15,13 +15,14 @@ namespace QuickMedia {
class SearchBar {
public:
SearchBar(sf::Font &font, sf::Texture &plugin_logo);
- void draw(sf::RenderWindow &window);
+ void draw(sf::RenderWindow &window, bool draw_shadow = true);
void update();
void onWindowResize(const sf::Vector2f &window_size);
void onTextEntered(sf::Uint32 codepoint);
void clear();
float getBottom() const;
+ float getBottomWithoutShadow() const;
TextUpdateCallback onTextUpdateCallback;
TextSubmitCallback onTextSubmitCallback;
diff --git a/include/Storage.hpp b/include/Storage.hpp
index 5b35ba7..4dd8db2 100644
--- a/include/Storage.hpp
+++ b/include/Storage.hpp
@@ -1,8 +1,13 @@
#pragma once
#include "Path.hpp"
+#include <functional>
+#include <filesystem>
namespace QuickMedia {
+ // Return false to stop the iterator
+ using FileIteratorCallback = std::function<bool(const std::filesystem::path &filepath)>;
+
enum class FileType {
FILE_NOT_FOUND,
REGULAR,
@@ -17,4 +22,5 @@ namespace QuickMedia {
int file_get_content(const Path &path, std::string &result);
int file_overwrite(const Path &path, const std::string &data);
int create_lock_file(const Path &path);
+ void for_files_in_dir(const Path &path, FileIteratorCallback callback);
} \ No newline at end of file
diff --git a/project.conf b/project.conf
index 38f11e3..f93e09e 100644
--- a/project.conf
+++ b/project.conf
@@ -4,6 +4,9 @@ type = "executable"
version = "0.1.0"
platforms = ["posix"]
+[lang.cpp]
+version = "c++17"
+
[dependencies]
sfml-graphics = "2"
x11 = "1.6.5"
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index 21d996d..db7e214 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -22,7 +22,6 @@
#include <X11/Xatom.h>
#include <signal.h>
-const sf::Color front_color(43, 45, 47);
const sf::Color back_color(30, 32, 34);
const int DOUBLE_CLICK_TIME = 500;
@@ -70,16 +69,16 @@ namespace QuickMedia {
delete current_plugin;
}
- static SearchResult search_selected_suggestion(Body *body, Plugin *plugin, std::string &selected_title, std::string &selected_url) {
- BodyItem *selected_item = body->get_selected();
+ static SearchResult search_selected_suggestion(Body *input_body, Body *output_body, Plugin *plugin, std::string &selected_title, std::string &selected_url) {
+ BodyItem *selected_item = input_body->get_selected();
if(!selected_item)
return SearchResult::ERR;
selected_title = selected_item->title;
selected_url = selected_item->url;
- body->clear_items();
- SearchResult search_result = plugin->search(!selected_url.empty() ? selected_url : selected_title, body->items);
- body->reset_selected();
+ output_body->clear_items();
+ SearchResult search_result = plugin->search(!selected_url.empty() ? selected_url : selected_title, output_body->items);
+ output_body->reset_selected();
return search_result;
}
@@ -219,7 +218,7 @@ namespace QuickMedia {
return 0;
}
- void Program::base_event_handler(sf::Event &event, Page previous_page) {
+ void Program::base_event_handler(sf::Event &event, Page previous_page, bool handle_keypress) {
if (event.type == sf::Event::Closed) {
current_page = Page::EXIT;
} else if(event.type == sf::Event::Resized) {
@@ -227,7 +226,7 @@ namespace QuickMedia {
window_size.y = event.size.height;
sf::FloatRect visible_area(0, 0, window_size.x, window_size.y);
window.setView(sf::View(visible_area));
- } else if(event.type == sf::Event::KeyPressed) {
+ } else if(handle_keypress && event.type == sf::Event::KeyPressed) {
if(event.key.code == sf::Keyboard::Up) {
body->select_previous_item();
} else if(event.key.code == sf::Keyboard::Down) {
@@ -272,20 +271,24 @@ namespace QuickMedia {
return cppcodec::base64_rfc4648::encode(data);
}
- static bool get_manga_storage_json(const Path &storage_path, Json::Value &result) {
+ static std::string base64_decode(const std::string &data) {
+ return cppcodec::base64_rfc4648::decode<std::string>(data);
+ }
+
+ static bool read_file_as_json(const Path &storage_path, Json::Value &result) {
std::string file_content;
if(file_get_content(storage_path, file_content) != 0)
- return -1;
+ return false;
Json::CharReaderBuilder json_builder;
std::unique_ptr<Json::CharReader> json_reader(json_builder.newCharReader());
std::string json_errors;
- if(json_reader->parse(&file_content.front(), &file_content.back(), &result, &json_errors)) {
+ if(!json_reader->parse(file_content.data(), file_content.data() + file_content.size(), &result, &json_errors)) {
fprintf(stderr, "Failed to read json, error: %s\n", json_errors.c_str());
- return -1;
+ return false;
}
- return 0;
+ return true;
}
static bool save_manga_progress_json(const Path &path, const Json::Value &json) {
@@ -295,9 +298,7 @@ namespace QuickMedia {
static bool manga_extract_id_from_url(const std::string &url, std::string &manga_id) {
bool manganelo_website = false;
- if(url.find("mangakakalot") != std::string::npos)
- manganelo_website = true;
- else if(url.find("manganelo") != std::string::npos)
+ if(url.find("mangakakalot") != std::string::npos || url.find("manganelo") != std::string::npos)
manganelo_website = true;
if(manganelo_website) {
@@ -328,16 +329,66 @@ namespace QuickMedia {
}
}
+ enum class SearchSuggestionTab {
+ ALL,
+ HISTORY
+ };
+
void Program::search_suggestion_page() {
std::string update_search_text;
bool search_running = false;
- search_bar->onTextUpdateCallback = [&update_search_text](const std::string &text) {
- update_search_text = text;
+
+ Body history_body(this, font);
+ const float tab_text_size = 18.0f;
+ const float tab_height = tab_text_size + 10.0f;
+ sf::Text all_tab_text("All", font, tab_text_size);
+ sf::Text history_tab_text("History", font, tab_text_size);
+
+ struct Tab {
+ Body *body;
+ SearchSuggestionTab tab;
+ sf::Text *text;
};
- search_bar->onTextSubmitCallback = [this](const std::string &text) -> bool {
+ std::array<Tab, 2> tabs = { Tab{body, SearchSuggestionTab::ALL, &all_tab_text}, Tab{&history_body, SearchSuggestionTab::HISTORY, &history_tab_text} };
+ int selected_tab = 0;
+
+ // TOOD: Make generic, instead of checking for plugin
+ if(current_plugin->name == "manganelo") {
+ // TODO: Make asynchronous
+ for_files_in_dir(get_storage_dir().join("manga"), [&history_body](const std::filesystem::path &filepath) {
+ Path fullpath(filepath.c_str());
+ Json::Value body;
+ if(!read_file_as_json(fullpath, body)) {
+ fprintf(stderr, "Failed to read json file: %s\n", fullpath.data.c_str());
+ return true;
+ }
+
+ auto filename = filepath.filename();
+ const Json::Value &manga_name = body["name"];
+ if(!filename.empty() && manga_name.isString()) {
+ // TODO: Add thumbnail
+ auto body_item = std::make_unique<BodyItem>(manga_name.asString());
+ body_item->url = "https://manganelo.com/manga/" + base64_decode(filename.string());
+ history_body.items.push_back(std::move(body_item));
+ }
+ return true;
+ });
+ }
+
+ search_bar->onTextUpdateCallback = [&update_search_text, this, &tabs, &selected_tab](const std::string &text) {
+ if(tabs[selected_tab].body == body)
+ update_search_text = text;
+ else {
+ tabs[selected_tab].body->filter_search_fuzzy(text);
+ tabs[selected_tab].body->selected_item = 0;
+ }
+ };
+
+ search_bar->onTextSubmitCallback = [this, &tabs, &selected_tab](const std::string &text) -> bool {
Page next_page = current_plugin->get_page_after_search();
- if(search_selected_suggestion(body, current_plugin, content_title, content_url) != SearchResult::OK)
+ // TODO: This shouldn't be done if search_selected_suggestion fails
+ if(search_selected_suggestion(tabs[selected_tab].body, body, current_plugin, content_title, content_url) != SearchResult::OK)
return false;
if(next_page == Page::EPISODE_LIST) {
@@ -362,7 +413,7 @@ namespace QuickMedia {
content_storage_json["name"] = content_title;
FileType file_type = get_file_type(content_storage_file);
if(file_type == FileType::REGULAR)
- get_manga_storage_json(content_storage_file, content_storage_json);
+ read_file_as_json(content_storage_file, content_storage_json);
} else if(next_page == Page::VIDEO_CONTENT) {
watched_videos.clear();
if(content_url.empty())
@@ -381,26 +432,46 @@ namespace QuickMedia {
sf::Vector2f body_size;
bool resized = true;
sf::Event event;
+
+ const sf::Color tab_selected_color(0, 85, 119);
+ const sf::Color tab_unselected_color(43, 45, 47);
+ sf::RectangleShape tab_spacing_rect(sf::Vector2f(0.0f, 0.0f));
+ tab_spacing_rect.setFillColor(tab_unselected_color);
+ const float tab_spacer_height = 1.0f;
while (current_page == Page::SEARCH_SUGGESTION) {
while (window.pollEvent(event)) {
- base_event_handler(event, Page::EXIT);
+ base_event_handler(event, Page::EXIT, false);
if(event.type == sf::Event::Resized)
resized = true;
+ else if(event.type == sf::Event::KeyPressed) {
+ if(event.key.code == sf::Keyboard::Up) {
+ tabs[selected_tab].body->select_previous_item();
+ } else if(event.key.code == sf::Keyboard::Down) {
+ tabs[selected_tab].body->select_next_item();
+ } else if(event.key.code == sf::Keyboard::Escape) {
+ current_page = Page::EXIT;
+ } else if(event.key.code == sf::Keyboard::Left) {
+ selected_tab = std::max(0, selected_tab - 1);
+ } else if(event.key.code == sf::Keyboard::Right) {
+ selected_tab = std::min((int)tabs.size() - 1, selected_tab + 1);
+ }
+ }
}
if(resized) {
+ resized = false;
search_bar->onWindowResize(window_size);
float body_padding_horizontal = 50.0f;
- float body_padding_vertical = 50.0f;
+ float body_padding_vertical = tab_spacer_height + tab_height + 50.0f;
float body_width = window_size.x - body_padding_horizontal * 2.0f;
if(body_width < 400) {
body_width = window_size.x;
body_padding_horizontal = 0.0f;
}
- float search_bottom = search_bar->getBottom();
+ float search_bottom = search_bar->getBottomWithoutShadow();
body_pos = sf::Vector2f(body_padding_horizontal, search_bottom + body_padding_vertical);
body_size = sf::Vector2f(body_width, window_size.y - search_bottom);
}
@@ -428,8 +499,30 @@ namespace QuickMedia {
}
window.clear(back_color);
- body->draw(window, body_pos, body_size);
- search_bar->draw(window);
+ {
+ tab_spacing_rect.setPosition(0.0f, search_bar->getBottomWithoutShadow());
+ tab_spacing_rect.setSize(sf::Vector2f(window_size.x, tab_spacer_height));
+ window.draw(tab_spacing_rect);
+ const float width_per_tab = window_size.x / tabs.size();
+ const float tab_y = tab_spacer_height + std::floor(search_bar->getBottomWithoutShadow() + tab_height * 0.5f - (tab_text_size + 5.0f) * 0.5f);
+ sf::RectangleShape tab_background(sf::Vector2f(std::floor(width_per_tab), tab_height));
+ int i = 0;
+ for(Tab &tab : tabs) {
+ if(i == selected_tab)
+ tab_background.setFillColor(tab_selected_color);
+ else
+ tab_background.setFillColor(tab_unselected_color);
+ tab_background.setPosition(std::floor(i * width_per_tab), tab_spacer_height + std::floor(search_bar->getBottomWithoutShadow()));
+ window.draw(tab_background);
+ const float center = (i * width_per_tab) + (width_per_tab * 0.5f);
+ tab.text->setPosition(std::floor(center - tab.text->getLocalBounds().width * 0.5f), tab_y);
+ window.draw(*tab.text);
+ if(i == selected_tab)
+ tab.body->draw(window, body_pos, body_size);
+ ++i;
+ }
+ }
+ search_bar->draw(window, false);
window.display();
}
}
@@ -461,6 +554,7 @@ namespace QuickMedia {
}
if(resized) {
+ resized = false;
search_bar->onWindowResize(window_size);
float body_padding_horizontal = 50.0f;
@@ -816,6 +910,7 @@ namespace QuickMedia {
// TODO: This code is duplicated in many places. Handle it in one place.
if(resized) {
+ resized = false;
search_bar->onWindowResize(window_size);
float body_padding_horizontal = 50.0f;
@@ -1091,6 +1186,7 @@ namespace QuickMedia {
content_size.y = window_size.y - background_height;
if(resized) {
+ resized = false;
if(error) {
auto bounds = error_message.getLocalBounds();
error_message.setPosition(std::floor(content_size.x * 0.5f - bounds.width * 0.5f), std::floor(content_size.y * 0.5f - bounds.height));
@@ -1163,6 +1259,7 @@ namespace QuickMedia {
// TODO: This code is duplicated in many places. Handle it in one place.
if(resized) {
+ resized = false;
search_bar->onWindowResize(window_size);
float body_padding_horizontal = 50.0f;
@@ -1222,6 +1319,7 @@ namespace QuickMedia {
// TODO: This code is duplicated in many places. Handle it in one place.
if(resized) {
+ resized = false;
search_bar->onWindowResize(window_size);
float body_padding_horizontal = 50.0f;
diff --git a/src/SearchBar.cpp b/src/SearchBar.cpp
index 179f1d9..78cacb9 100644
--- a/src/SearchBar.cpp
+++ b/src/SearchBar.cpp
@@ -30,8 +30,9 @@ namespace QuickMedia {
plugin_logo_sprite.setTexture(plugin_logo, true);
}
- void SearchBar::draw(sf::RenderWindow &window) {
- window.draw(background_shadow);
+ void SearchBar::draw(sf::RenderWindow &window, bool draw_shadow) {
+ if(draw_shadow)
+ window.draw(background_shadow);
window.draw(shade);
window.draw(background);
window.draw(text);
@@ -129,4 +130,8 @@ namespace QuickMedia {
float SearchBar::getBottom() const {
return shade.getSize().y + background_shadow.getSize().y;
}
+
+ float SearchBar::getBottomWithoutShadow() const {
+ return shade.getSize().y;
+ }
} \ No newline at end of file
diff --git a/src/Storage.cpp b/src/Storage.cpp
index 1a199d9..ddfdb11 100644
--- a/src/Storage.cpp
+++ b/src/Storage.cpp
@@ -1,6 +1,7 @@
#include "../include/Storage.hpp"
#include "../include/env.hpp"
#include <stdio.h>
+#include <assert.h>
#if OS_FAMILY == OS_FAMILY_POSIX
#include <pwd.h>
@@ -88,6 +89,7 @@ namespace QuickMedia {
}
int file_get_content(const Path &path, std::string &result) {
+ assert(get_file_type(path) == FileType::REGULAR);
FILE *file = fopen(path.data.c_str(), "rb");
if(!file)
return -errno;
@@ -122,4 +124,11 @@ namespace QuickMedia {
return errno;
return close(fd);
}
+
+ void for_files_in_dir(const Path &path, FileIteratorCallback callback) {
+ for(auto &p : std::filesystem::directory_iterator(path.data)) {
+ if(!callback(p.path()))
+ break;
+ }
+ }
} \ No newline at end of file