diff --git a/resources.qrc b/resources.qrc
index cb74c2e42..3bd9144c1 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -401,5 +401,16 @@
resources/images/check_circle_green_icon.svg
resources/images/info_circle_icon.svg
resources/images/light_mode/sidebar/settings_icon.svg
+ resources/images/mobile/arrow_left.svg
+ resources/images/mobile/folder.svg
+ resources/images/mobile/profile.svg
+ resources/images/mobile/brush.svg
+ resources/images/mobile/sign_out.svg
+ resources/images/mobile/library.svg
+ resources/images/mobile/server.svg
+ resources/images/mobile/explore.svg
+ resources/images/mobile/refresh.svg
+ resources/images/mobile/diamond.svg
+ resources/images/mobile/gear.svg
diff --git a/resources/images/mobile/arrow_left.svg b/resources/images/mobile/arrow_left.svg
new file mode 100644
index 000000000..124f98587
--- /dev/null
+++ b/resources/images/mobile/arrow_left.svg
@@ -0,0 +1,4 @@
+
diff --git a/resources/images/mobile/brush.svg b/resources/images/mobile/brush.svg
new file mode 100644
index 000000000..8bfb0ea2f
--- /dev/null
+++ b/resources/images/mobile/brush.svg
@@ -0,0 +1,3 @@
+
diff --git a/resources/images/mobile/diamond.svg b/resources/images/mobile/diamond.svg
new file mode 100644
index 000000000..61bfb021f
--- /dev/null
+++ b/resources/images/mobile/diamond.svg
@@ -0,0 +1,3 @@
+
diff --git a/resources/images/mobile/explore.svg b/resources/images/mobile/explore.svg
new file mode 100644
index 000000000..db3de2935
--- /dev/null
+++ b/resources/images/mobile/explore.svg
@@ -0,0 +1,3 @@
+
diff --git a/resources/images/mobile/folder.svg b/resources/images/mobile/folder.svg
new file mode 100644
index 000000000..c64b35e2e
--- /dev/null
+++ b/resources/images/mobile/folder.svg
@@ -0,0 +1,3 @@
+
diff --git a/resources/images/mobile/gear.svg b/resources/images/mobile/gear.svg
new file mode 100644
index 000000000..ff15640f3
--- /dev/null
+++ b/resources/images/mobile/gear.svg
@@ -0,0 +1,3 @@
+
diff --git a/resources/images/mobile/library.svg b/resources/images/mobile/library.svg
new file mode 100644
index 000000000..7162189ed
--- /dev/null
+++ b/resources/images/mobile/library.svg
@@ -0,0 +1,3 @@
+
diff --git a/resources/images/mobile/profile.svg b/resources/images/mobile/profile.svg
new file mode 100644
index 000000000..6b9d8f8cf
--- /dev/null
+++ b/resources/images/mobile/profile.svg
@@ -0,0 +1,3 @@
+
diff --git a/resources/images/mobile/refresh.svg b/resources/images/mobile/refresh.svg
new file mode 100644
index 000000000..fc09007ce
--- /dev/null
+++ b/resources/images/mobile/refresh.svg
@@ -0,0 +1,3 @@
+
diff --git a/resources/images/mobile/server.svg b/resources/images/mobile/server.svg
new file mode 100644
index 000000000..7a0ea9ef1
--- /dev/null
+++ b/resources/images/mobile/server.svg
@@ -0,0 +1,4 @@
+
diff --git a/resources/images/mobile/sign_out.svg b/resources/images/mobile/sign_out.svg
new file mode 100644
index 000000000..acbbef89d
--- /dev/null
+++ b/resources/images/mobile/sign_out.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a912e4882..9366d3e29 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,6 +1,9 @@
# Configuration
-set(QML_IMPORT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/presentation/modules ${CMAKE_CURRENT_BINARY_DIR}/" CACHE STRING "Qml modules")
-
+if(ANDROID)
+ set(QML_IMPORT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/presentation/mobile/modules CACHE STRING "Qml modules" FORCE)
+else()
+ set(QML_IMPORT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/presentation/desktop/modules CACHE STRING "Qml modules" FORCE)
+endif()
# Language
set(CMAKE_CXX_STANDARD 20)
@@ -47,7 +50,6 @@ if(ANDROID)
list(APPEND CUSTOM_ANDROID_EXTRA_LIBS "${PROJECT_SOURCE_DIR}/libs/mupdf/build/shared-debug/libmupdf.so")
list(APPEND CUSTOM_ANDROID_EXTRA_LIBS "${PROJECT_SOURCE_DIR}/libs/mupdf/build/shared-debug/libmupdfcpp.so")
string(REPLACE ";" "," CUSTOM_ANDROID_EXTRA_LIBS_STRING "${CUSTOM_ANDROID_EXTRA_LIBS}")
-
set_property(TARGET librum PROPERTY QT_ANDROID_EXTRA_LIBS
${ANDROID_SDK_ROOT}/android_openssl/ssl_3/arm64-v8a/libcrypto_3.so
${ANDROID_SDK_ROOT}/android_openssl/ssl_3/arm64-v8a/libssl_3.so
diff --git a/src/adapters/controllers/page_controller.cpp b/src/adapters/controllers/page_controller.cpp
index 65ac8502a..f34d13878 100644
--- a/src/adapters/controllers/page_controller.cpp
+++ b/src/adapters/controllers/page_controller.cpp
@@ -1,4 +1,5 @@
#include "page_controller.hpp"
+#include
#include "fz_utils.hpp"
#include "mupdf/classes.h"
@@ -54,11 +55,13 @@ void PageController::setInvertColor(bool newInvertColor)
m_pageImageOutdated = true;
}
-QImage PageController::renderPage()
+QImage PageController::renderPage(int pageNr)
{
if(!m_pageImageOutdated)
return m_pageImage;
+ qDebug() << "Re-rendering page nr: " << pageNr;
+
auto zoom = m_matrix.a;
m_pageImage = utils::qImageFromPixmap(m_pageGenerator.renderPage(zoom));
diff --git a/src/adapters/controllers/page_controller.hpp b/src/adapters/controllers/page_controller.hpp
index 74ac6ed43..cbd819d81 100644
--- a/src/adapters/controllers/page_controller.hpp
+++ b/src/adapters/controllers/page_controller.hpp
@@ -25,7 +25,7 @@ class PageController : public IPageController
void setInvertColor(bool newInvertColor) override;
- QImage renderPage() override;
+ QImage renderPage(int pageNr) override;
bool pointIsAboveText(const QPointF& point) override;
bool pointIsAboveLink(const QPointF& point) override;
diff --git a/src/adapters/interfaces/controllers/i_page_controller.hpp b/src/adapters/interfaces/controllers/i_page_controller.hpp
index 3db5b1fb4..9ded9788b 100644
--- a/src/adapters/interfaces/controllers/i_page_controller.hpp
+++ b/src/adapters/interfaces/controllers/i_page_controller.hpp
@@ -29,7 +29,7 @@ class ADAPTERS_EXPORT IPageController : public QObject
virtual void setInvertColor(bool newInvertColor) = 0;
- virtual QImage renderPage() = 0;
+ virtual QImage renderPage(int pageNr) = 0;
virtual bool pointIsAboveText(const QPointF& point) = 0;
virtual bool pointIsAboveLink(const QPointF& point) = 0;
diff --git a/src/adapters/interfaces/persistance/i_ai_explanation_access.hpp b/src/adapters/interfaces/persistance/i_ai_explanation_access.hpp
index a1e65cd69..e74923653 100644
--- a/src/adapters/interfaces/persistance/i_ai_explanation_access.hpp
+++ b/src/adapters/interfaces/persistance/i_ai_explanation_access.hpp
@@ -7,7 +7,7 @@ namespace adapters
{
/**
- * The AiExplanation class makes the API calls to the server to get the
+ * The IAiExplanationAccess class makes the API calls to the server to get the
* explanation for text.
*/
class ADAPTERS_EXPORT IAiExplanationAccess : public QObject
@@ -25,4 +25,4 @@ class ADAPTERS_EXPORT IAiExplanationAccess : public QObject
void errorOccured(int errorCode) const;
};
-} // namespace adapters
\ No newline at end of file
+} // namespace adapters
diff --git a/src/adapters/interfaces/persistance/i_app_info_access.hpp b/src/adapters/interfaces/persistance/i_app_info_access.hpp
index d9bb0d181..92a71cdf6 100644
--- a/src/adapters/interfaces/persistance/i_app_info_access.hpp
+++ b/src/adapters/interfaces/persistance/i_app_info_access.hpp
@@ -7,8 +7,8 @@ namespace adapters
{
/**
- * The AppInfo class makes the API calls to the server to query metainformation
- * about Librum.
+ * The IAppInfoAccess class makes the API calls to the server to query
+ * metainformation about Librum.
*/
class ADAPTERS_EXPORT IAppInfoAccess : public QObject
{
diff --git a/src/adapters/interfaces/persistance/i_authentication_access.hpp b/src/adapters/interfaces/persistance/i_authentication_access.hpp
index d2f2221ae..551470bb6 100644
--- a/src/adapters/interfaces/persistance/i_authentication_access.hpp
+++ b/src/adapters/interfaces/persistance/i_authentication_access.hpp
@@ -9,7 +9,7 @@ namespace adapters
{
/**
- * The LibraryStorageAccess class makes the API calls to the book storage
+ * The IAuthenticationAccess class makes the API calls to the authentication
* server.
*/
class ADAPTERS_EXPORT IAuthenticationAccess : public QObject
diff --git a/src/adapters/interfaces/persistance/i_dictionary_access.hpp b/src/adapters/interfaces/persistance/i_dictionary_access.hpp
index a3e9b5386..8f64e222d 100644
--- a/src/adapters/interfaces/persistance/i_dictionary_access.hpp
+++ b/src/adapters/interfaces/persistance/i_dictionary_access.hpp
@@ -8,7 +8,7 @@ namespace adapters
{
/**
- * The DictionaryAccess class makes the API calls to the API of the used
+ * The IDictionaryAccess class makes the API calls to the API of the used
* dictionary to retrieve definitions, etc. from it.
*/
class ADAPTERS_EXPORT IDictionaryAccess : public QObject
diff --git a/src/adapters/interfaces/persistance/i_folder_storage_access.hpp b/src/adapters/interfaces/persistance/i_folder_storage_access.hpp
index 18c6778be..6e2e01bab 100644
--- a/src/adapters/interfaces/persistance/i_folder_storage_access.hpp
+++ b/src/adapters/interfaces/persistance/i_folder_storage_access.hpp
@@ -8,7 +8,7 @@ namespace adapters
{
/**
- * The FolderStorageAccess class makes the API calls to the API of the used
+ * The IFolderStorageAccess class makes the API calls to the API of the used
* Folder to store folders, etc. from it.
*/
class ADAPTERS_EXPORT IFolderStorageAccess : public QObject
diff --git a/src/adapters/interfaces/persistance/i_library_storage_access.hpp b/src/adapters/interfaces/persistance/i_library_storage_access.hpp
index 8549d07c6..6e4c3a692 100644
--- a/src/adapters/interfaces/persistance/i_library_storage_access.hpp
+++ b/src/adapters/interfaces/persistance/i_library_storage_access.hpp
@@ -9,7 +9,7 @@ namespace adapters
{
/**
- * The IAuthenticationAccess class makes the API calls to the authentication
+ * The ILibraryStorageAccess class makes the API calls to the book storage
* server.
*/
class ADAPTERS_EXPORT ILibraryStorageAccess : public QObject
diff --git a/src/adapters/interfaces/persistance/i_user_storage_access.hpp b/src/adapters/interfaces/persistance/i_user_storage_access.hpp
index b56268f34..66ce0da4e 100644
--- a/src/adapters/interfaces/persistance/i_user_storage_access.hpp
+++ b/src/adapters/interfaces/persistance/i_user_storage_access.hpp
@@ -10,7 +10,7 @@ namespace adapters
{
/**
- * The UserStorageAccess class makes the API calls to the user storage server.
+ * The IUserStorageAccess class makes the API calls to the user storage server.
*/
class ADAPTERS_EXPORT IUserStorageAccess : public QObject
{
diff --git a/src/application/CMakeLists.txt b/src/application/CMakeLists.txt
index 0a41c5fd4..af4af4222 100644
--- a/src/application/CMakeLists.txt
+++ b/src/application/CMakeLists.txt
@@ -102,15 +102,17 @@ if(NOT ${NO_VENV})
set(VENV_OPTION "--venv")
endif()
+set(BUILD_MUTOOL true)
if(ANDROID)
set(EXTRA_MAKE_AGRS --m-vars 'HAVE_OBJCOPY=no HAVE_LIBCRYPTO=no')
+ set(BUILD_MUTOOL false)
endif()
if(UNIX)
set(MUPDF_OUTPUT_DIR "${PROJECT_SOURCE_DIR}/libs/mupdf/build/$,shared-debug,shared-release>")
set(MUPDF_OUTPUT "${MUPDF_OUTPUT_DIR}/libmupdfcpp.so")
set(MUPDF_OUTPUT "${MUPDF_OUTPUT_DIR}/libmupdfcpp.so" PARENT_SCOPE)
- set(MUPDF_BUILD_COMMAND ./scripts/mupdfwrap.py ${VENV_OPTION} -d build/$,shared-debug,shared-release> -b --m-target 'libs tools' ${EXTRA_MAKE_AGRS} -j 0 m01)
+ set(MUPDF_BUILD_COMMAND ./scripts/mupdfwrap.py ${VENV_OPTION} -d build/$,shared-debug,shared-release> -b --m-target 'libs $<$:tools>' ${EXTRA_MAKE_AGRS} -j 0 m01)
elseif(WIN32)
set(MUPDF_OUTPUT_DIR "${PROJECT_SOURCE_DIR}/libs/mupdf/platform/win32/x64/$,Debug,Release>")
set(MUPDF_OUTPUT "${MUPDF_OUTPUT_DIR}/mupdfcpp64.lib" PARENT_SCOPE)
@@ -125,9 +127,8 @@ set(CC_COMMAND "${CMAKE_C_COMPILER}")
set(CXX_COMMAND "${CMAKE_CXX_COMPILER}")
if(ANDROID)
- string(APPEND CC_COMMAND " --target=aarch64-none-linux-android23 --sysroot=/home/creapermann/Android/Sdk/ndk/26.1.10909125/toolchains/llvm/prebuilt/linux-x86_64/sysroot")
- string(APPEND CXX_COMMAND " --target=aarch64-none-linux-android23 --sysroot=/home/creapermann/Android/Sdk/ndk/26.1.10909125/toolchains/llvm/prebuilt/linux-x86_64/sysroot")
- set(ANDROID_COMPILERS "CC=${CC_COMMAND}" "CXX=${CXX_COMMAND}")
+ string(APPEND CC_COMMAND " --target=aarch64-none-linux-android23 --sysroot=${ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/sysroot")
+ string(APPEND CXX_COMMAND " --target=aarch64-none-linux-android23 --sysroot=${ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/sysroot")
endif()
@@ -145,22 +146,30 @@ else()
set(EXECUTABLE_EXTENSION ".exe")
endif()
+# Build mupdf
add_custom_target(mupdf
- # Build mupdf
COMMAND ${CMAKE_COMMAND} -E env
- ${ANDROID_COMPILERS}
+ $<$>:CC=${CC_COMMAND}>
+ $<$>:CXX=${CXX_COMMAND}>
"USE_SYSTEM_LIBJPEG=${USE_SYSTEM_LIBJPEG_VALUE}"
"USE_SONAME=no"
${MUPDF_BUILD_COMMAND}
- # Copy mutool to the build directory
- COMMAND ${CMAKE_COMMAND} -E copy
- "${MUPDF_OUTPUT_DIR}/mutool${EXECUTABLE_EXTENSION}"
- "${PROJECT_BINARY_DIR}/mutool${EXECUTABLE_EXTENSION}"
BYPRODUCTS ${MUPDF_OUTPUT}
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/libs/mupdf
COMMENT "Building mupdf (This takes a while) ..."
)
+# Copy the mutool executable to the build directory
+if(BUILD_MUTOOL)
+ add_custom_command(
+ TARGET mupdf POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy
+ "${MUPDF_OUTPUT_DIR}/mutool${EXECUTABLE_EXTENSION}"
+ "${PROJECT_BINARY_DIR}/mutool${EXECUTABLE_EXTENSION}"
+ BYPRODUCTS "${PROJECT_BINARY_DIR}/mutool${EXECUTABLE_EXTENSION}"
+ DEPENDS ${MUPDF_OUTPUT}
+ )
+endif()
#Copy the mupdf dlls to the build directory for windows
if(WIN32)
diff --git a/src/main.cpp b/src/main.cpp
index f3ee09d66..2c78cb7dc 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -29,9 +29,7 @@
#include "i_free_books_service.hpp"
#include "i_library_service.hpp"
#include "i_user_service.hpp"
-#include "key_sequence_recorder.hpp"
#include "library_proxy_model.hpp"
-#include "message_handler.hpp"
#include "page_view.hpp"
#include "setting_groups.hpp"
#include "setting_keys.hpp"
@@ -41,6 +39,11 @@
#include "user_controller.hpp"
#include "word_definition_dto.hpp"
+#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
+ #include "key_sequence_recorder.hpp"
+ #include "message_handler.hpp"
+#endif
+
using namespace adapters::controllers;
using namespace application::services;
@@ -63,7 +66,9 @@ int main(int argc, char* argv[])
QIcon icon(":/src/logo.ico");
app.setWindowIcon(icon);
+#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
qInstallMessageHandler(logging::messageHandler);
+#endif
setupGlobalSettings();
setupFonts();
@@ -79,14 +84,18 @@ int main(int argc, char* argv[])
qmlRegisterType("Librum.models", 1, 0, "BookTitleModel");
qmlRegisterType("Librum.models", 1, 0, "FreeBooksModel");
qmlRegisterType("Librum.models", 1, 0, "ShortcutsProxyModel");
- qmlRegisterType("Librum.elements", 1, 0, "KeySequenceRecorder");
- qmlRegisterType("Librum.elements", 1, 0, "PageView");
qRegisterMetaType();
qRegisterMetaType();
qRegisterMetaType();
qRegisterMetaType();
qRegisterMetaType();
qRegisterMetaType();
+ qmlRegisterType("Librum.elements", 1, 0, "PageView");
+
+#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
+ qmlRegisterType("Librum.elements", 1, 0, "KeySequenceRecorder");
+#elif defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
+#endif
// Authentication Stack
@@ -242,23 +251,23 @@ int main(int argc, char* argv[])
// Setup translations
QSettings settings;
auto storedLanguage = settings.value("language", QVariant("")).toString();
- if(storedLanguage.isEmpty())
- {
- // If no language was specified in the settings, deduce the system language
- const QStringList uiLanguages = QLocale::system().uiLanguages();
- for(const QString& locale : uiLanguages)
- {
- const QString name = QLocale(locale).name();
- if(appInfoController->switchToLanguage(name))
- {
- break;
- }
- }
- }
- else
- {
- appInfoController->switchToLanguage(storedLanguage);
- }
+ // if(storedLanguage.isEmpty())
+ // {
+ // // If no language was specified in the settings, deduce the system language
+ // const QStringList uiLanguages = QLocale::system().uiLanguages();
+ // for(const QString& locale : uiLanguages)
+ // {
+ // const QString name = QLocale(locale).name();
+ // if(appInfoController->switchToLanguage(name))
+ // {
+ // break;
+ // }
+ // }
+ // }
+ // else
+ // {
+ // appInfoController->switchToLanguage(storedLanguage);
+ // }
if(app.arguments().size() == 2)
{
diff --git a/src/presentation/CMakeLists.txt b/src/presentation/CMakeLists.txt
index 8e78eacd2..973e7489c 100644
--- a/src/presentation/CMakeLists.txt
+++ b/src/presentation/CMakeLists.txt
@@ -2,14 +2,36 @@ set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
# Build
+if(ANDROID)
+ set(QML_SOURCE_QRC_PATH "mobile/qmlSourcesMobile.qrc")
+else()
+ set(QML_SOURCE_QRC_PATH "desktop/qmlSourcesDesktop.qrc")
+endif()
+
+if(ANDROID)
+ set(CPP_SOURCE_FOLDER "${CMAKE_CURRENT_SOURCE_DIR}/mobile/modules/CppElements")
+ set(CPP_FILES
+ mobile/modules/CppElements/page_view.hpp
+ mobile/modules/CppElements/page_view.cpp
+ )
+else()
+ set(CPP_SOURCE_FOLDER "${CMAKE_CURRENT_SOURCE_DIR}/desktop/modules/CppElements")
+ set(CPP_FILES
+ desktop/modules/CppElements/key_sequence_recorder.cpp
+ desktop/modules/CppElements/key_sequence_recorder.hpp
+ desktop/modules/CppElements/page_view.cpp
+ desktop/modules/CppElements/page_view.hpp
+ )
+endif()
+
add_library(presentation
SHARED
${PROJECT_SOURCE_DIR}/resources.qrc
- qmlSources.qrc
- modules/CppElements/key_sequence_recorder.cpp
- modules/CppElements/key_sequence_recorder.hpp
- modules/CppElements/page_view.cpp
- modules/CppElements/page_view.hpp
+ ${QML_SOURCE_QRC_PATH}
+ qmlSourcesGeneral.qrc
+ ${CPP_FILES}
+ mobile/modules/CppElements/page_view.hpp mobile/modules/CppElements/page_view.cpp
+ mobile/modules/CppElements/presentation_export.hpp
)
@@ -50,7 +72,7 @@ target_link_libraries(presentation
target_include_directories(presentation
PUBLIC
- ${CMAKE_CURRENT_SOURCE_DIR}/modules/CppElements
+ ${CPP_SOURCE_FOLDER}
${APPLICATION_INCLUDES}
${ADAPTERS_INCLUDES}
)
diff --git a/src/presentation/FontSheet.qml b/src/presentation/FontSheet.qml
index 36b2e4e62..213221d2b 100644
--- a/src/presentation/FontSheet.qml
+++ b/src/presentation/FontSheet.qml
@@ -32,11 +32,13 @@ Item {
property real size13dot5: systemSize + 4.5
property real size14: systemSize + 5
property real size15: systemSize + 6
+ property real size15dot5: systemSize + 6.5
property real size16: systemSize + 7
property real size16dot5: systemSize + 7.5
property real size17: systemSize + 8
property real size18: systemSize + 9
property real size19: systemSize + 10
+ property real size19dot5: systemSize + 10.5
property real size20: systemSize + 11
property real size21: systemSize + 12
property real size22: systemSize + 13
@@ -44,6 +46,7 @@ Item {
property real size25: systemSize + 16
property real size26: systemSize + 17
property real size28: systemSize + 19
+ property real size34: systemSize + 25
property real size42: systemSize + 33
property real size46: systemSize + 37
}
diff --git a/src/presentation/IconSheet.qml b/src/presentation/IconSheet.qml
index cd323ceba..70346ca3c 100644
--- a/src/presentation/IconSheet.qml
+++ b/src/presentation/IconSheet.qml
@@ -138,6 +138,19 @@ Item {
property string bookClosed
property string pen
+ // Mobile
+ property string mobileLibrary: "/resources/images/mobile/library.svg"
+ property string mobileExplore: "/resources/images/mobile/explore.svg"
+ property string mobileProfile: "/resources/images/mobile/profile.svg"
+ property string mobileFolder: "/resources/images/mobile/folder.svg"
+ property string mobileBrush: "/resources/images/mobile/brush.svg"
+ property string mobileDiamond: "/resources/images/mobile/diamond.svg"
+ property string mobileRefresh: "/resources/images/mobile/refresh.svg"
+ property string mobileServer: "/resources/images/mobile/server.svg"
+ property string mobileGear: "/resources/images/mobile/gear.svg"
+ property string mobileSignOut: "/resources/images/mobile/sign_out.svg"
+ property string mobileArrowLeft: "/resources/images/mobile/arrow_left.svg"
+
state: "Light"
states: [
State {
diff --git a/src/presentation/PageNavigationLogic.js b/src/presentation/desktop/PageNavigationLogic.js
similarity index 100%
rename from src/presentation/PageNavigationLogic.js
rename to src/presentation/desktop/PageNavigationLogic.js
diff --git a/src/presentation/forgotPasswordPage/MForgotPasswordPage.qml b/src/presentation/desktop/forgotPasswordPage/MForgotPasswordPage.qml
similarity index 100%
rename from src/presentation/forgotPasswordPage/MForgotPasswordPage.qml
rename to src/presentation/desktop/forgotPasswordPage/MForgotPasswordPage.qml
diff --git a/src/presentation/freeBooksPage/MDownloadBookPopup.qml b/src/presentation/desktop/freeBooksPage/MDownloadBookPopup.qml
similarity index 100%
rename from src/presentation/freeBooksPage/MDownloadBookPopup.qml
rename to src/presentation/desktop/freeBooksPage/MDownloadBookPopup.qml
diff --git a/src/presentation/freeBooksPage/MFreeBook.qml b/src/presentation/desktop/freeBooksPage/MFreeBook.qml
similarity index 100%
rename from src/presentation/freeBooksPage/MFreeBook.qml
rename to src/presentation/desktop/freeBooksPage/MFreeBook.qml
diff --git a/src/presentation/freeBooksPage/MFreeBooksPage.qml b/src/presentation/desktop/freeBooksPage/MFreeBooksPage.qml
similarity index 100%
rename from src/presentation/freeBooksPage/MFreeBooksPage.qml
rename to src/presentation/desktop/freeBooksPage/MFreeBooksPage.qml
diff --git a/src/presentation/freeBooksPage/explorerToolbar/MExplorerToolbar.qml b/src/presentation/desktop/freeBooksPage/explorerToolbar/MExplorerToolbar.qml
similarity index 100%
rename from src/presentation/freeBooksPage/explorerToolbar/MExplorerToolbar.qml
rename to src/presentation/desktop/freeBooksPage/explorerToolbar/MExplorerToolbar.qml
diff --git a/src/presentation/homePage/MBook.qml b/src/presentation/desktop/homePage/MBook.qml
similarity index 100%
rename from src/presentation/homePage/MBook.qml
rename to src/presentation/desktop/homePage/MBook.qml
diff --git a/src/presentation/homePage/MBookDetailsPopup.qml b/src/presentation/desktop/homePage/MBookDetailsPopup.qml
similarity index 100%
rename from src/presentation/homePage/MBookDetailsPopup.qml
rename to src/presentation/desktop/homePage/MBookDetailsPopup.qml
diff --git a/src/presentation/homePage/MBookMultiSelectRightClickPopup.qml b/src/presentation/desktop/homePage/MBookMultiSelectRightClickPopup.qml
similarity index 100%
rename from src/presentation/homePage/MBookMultiSelectRightClickPopup.qml
rename to src/presentation/desktop/homePage/MBookMultiSelectRightClickPopup.qml
diff --git a/src/presentation/homePage/MBookRightClickPopup.qml b/src/presentation/desktop/homePage/MBookRightClickPopup.qml
similarity index 100%
rename from src/presentation/homePage/MBookRightClickPopup.qml
rename to src/presentation/desktop/homePage/MBookRightClickPopup.qml
diff --git a/src/presentation/homePage/MEmptyScreenContent.qml b/src/presentation/desktop/homePage/MEmptyScreenContent.qml
similarity index 100%
rename from src/presentation/homePage/MEmptyScreenContent.qml
rename to src/presentation/desktop/homePage/MEmptyScreenContent.qml
diff --git a/src/presentation/homePage/MHomePage.qml b/src/presentation/desktop/homePage/MHomePage.qml
similarity index 100%
rename from src/presentation/homePage/MHomePage.qml
rename to src/presentation/desktop/homePage/MHomePage.qml
diff --git a/src/presentation/homePage/MNoBookSatisfiesFilterItem.qml b/src/presentation/desktop/homePage/MNoBookSatisfiesFilterItem.qml
similarity index 100%
rename from src/presentation/homePage/MNoBookSatisfiesFilterItem.qml
rename to src/presentation/desktop/homePage/MNoBookSatisfiesFilterItem.qml
diff --git a/src/presentation/homePage/folderSidebar/MAddFolderPopup.qml b/src/presentation/desktop/homePage/folderSidebar/MAddFolderPopup.qml
similarity index 100%
rename from src/presentation/homePage/folderSidebar/MAddFolderPopup.qml
rename to src/presentation/desktop/homePage/folderSidebar/MAddFolderPopup.qml
diff --git a/src/presentation/homePage/folderSidebar/MDeleteFolderPopup.qml b/src/presentation/desktop/homePage/folderSidebar/MDeleteFolderPopup.qml
similarity index 100%
rename from src/presentation/homePage/folderSidebar/MDeleteFolderPopup.qml
rename to src/presentation/desktop/homePage/folderSidebar/MDeleteFolderPopup.qml
diff --git a/src/presentation/homePage/folderSidebar/MDescriptionPopup.qml b/src/presentation/desktop/homePage/folderSidebar/MDescriptionPopup.qml
similarity index 100%
rename from src/presentation/homePage/folderSidebar/MDescriptionPopup.qml
rename to src/presentation/desktop/homePage/folderSidebar/MDescriptionPopup.qml
diff --git a/src/presentation/homePage/folderSidebar/MFolderSidebar.qml b/src/presentation/desktop/homePage/folderSidebar/MFolderSidebar.qml
similarity index 100%
rename from src/presentation/homePage/folderSidebar/MFolderSidebar.qml
rename to src/presentation/desktop/homePage/folderSidebar/MFolderSidebar.qml
diff --git a/src/presentation/homePage/folderSidebar/MFolderSidebarItem.qml b/src/presentation/desktop/homePage/folderSidebar/MFolderSidebarItem.qml
similarity index 100%
rename from src/presentation/homePage/folderSidebar/MFolderSidebarItem.qml
rename to src/presentation/desktop/homePage/folderSidebar/MFolderSidebarItem.qml
diff --git a/src/presentation/homePage/folderSidebar/MFolderSidebarItemRigthclickPopup.qml b/src/presentation/desktop/homePage/folderSidebar/MFolderSidebarItemRigthclickPopup.qml
similarity index 100%
rename from src/presentation/homePage/folderSidebar/MFolderSidebarItemRigthclickPopup.qml
rename to src/presentation/desktop/homePage/folderSidebar/MFolderSidebarItemRigthclickPopup.qml
diff --git a/src/presentation/homePage/folderSidebar/MMoveToFolderPopup.qml b/src/presentation/desktop/homePage/folderSidebar/MMoveToFolderPopup.qml
similarity index 100%
rename from src/presentation/homePage/folderSidebar/MMoveToFolderPopup.qml
rename to src/presentation/desktop/homePage/folderSidebar/MMoveToFolderPopup.qml
diff --git a/src/presentation/homePage/folderSidebar/MSelectColorPopup.qml b/src/presentation/desktop/homePage/folderSidebar/MSelectColorPopup.qml
similarity index 100%
rename from src/presentation/homePage/folderSidebar/MSelectColorPopup.qml
rename to src/presentation/desktop/homePage/folderSidebar/MSelectColorPopup.qml
diff --git a/src/presentation/homePage/folderSidebar/MSelectIconPopup.qml b/src/presentation/desktop/homePage/folderSidebar/MSelectIconPopup.qml
similarity index 100%
rename from src/presentation/homePage/folderSidebar/MSelectIconPopup.qml
rename to src/presentation/desktop/homePage/folderSidebar/MSelectIconPopup.qml
diff --git a/src/presentation/homePage/manageTagsPopup/MAddTagBox.qml b/src/presentation/desktop/homePage/manageTagsPopup/MAddTagBox.qml
similarity index 100%
rename from src/presentation/homePage/manageTagsPopup/MAddTagBox.qml
rename to src/presentation/desktop/homePage/manageTagsPopup/MAddTagBox.qml
diff --git a/src/presentation/homePage/manageTagsPopup/MAddTagBoxPopup.qml b/src/presentation/desktop/homePage/manageTagsPopup/MAddTagBoxPopup.qml
similarity index 100%
rename from src/presentation/homePage/manageTagsPopup/MAddTagBoxPopup.qml
rename to src/presentation/desktop/homePage/manageTagsPopup/MAddTagBoxPopup.qml
diff --git a/src/presentation/homePage/manageTagsPopup/MManageTagsPopup.qml b/src/presentation/desktop/homePage/manageTagsPopup/MManageTagsPopup.qml
similarity index 100%
rename from src/presentation/homePage/manageTagsPopup/MManageTagsPopup.qml
rename to src/presentation/desktop/homePage/manageTagsPopup/MManageTagsPopup.qml
diff --git a/src/presentation/homePage/manageTagsPopup/MTagItem.qml b/src/presentation/desktop/homePage/manageTagsPopup/MTagItem.qml
similarity index 100%
rename from src/presentation/homePage/manageTagsPopup/MTagItem.qml
rename to src/presentation/desktop/homePage/manageTagsPopup/MTagItem.qml
diff --git a/src/presentation/homePage/toolbar/MToolbar.qml b/src/presentation/desktop/homePage/toolbar/MToolbar.qml
similarity index 100%
rename from src/presentation/homePage/toolbar/MToolbar.qml
rename to src/presentation/desktop/homePage/toolbar/MToolbar.qml
diff --git a/src/presentation/homePage/toolbar/filterByButton/MFilterByButton.qml b/src/presentation/desktop/homePage/toolbar/filterByButton/MFilterByButton.qml
similarity index 100%
rename from src/presentation/homePage/toolbar/filterByButton/MFilterByButton.qml
rename to src/presentation/desktop/homePage/toolbar/filterByButton/MFilterByButton.qml
diff --git a/src/presentation/homePage/toolbar/filterByButton/MFilterByPopup.qml b/src/presentation/desktop/homePage/toolbar/filterByButton/MFilterByPopup.qml
similarity index 100%
rename from src/presentation/homePage/toolbar/filterByButton/MFilterByPopup.qml
rename to src/presentation/desktop/homePage/toolbar/filterByButton/MFilterByPopup.qml
diff --git a/src/presentation/homePage/toolbar/sortByButton/MSortByButton.qml b/src/presentation/desktop/homePage/toolbar/sortByButton/MSortByButton.qml
similarity index 100%
rename from src/presentation/homePage/toolbar/sortByButton/MSortByButton.qml
rename to src/presentation/desktop/homePage/toolbar/sortByButton/MSortByButton.qml
diff --git a/src/presentation/homePage/toolbar/sortByButton/MSortByPopup.qml b/src/presentation/desktop/homePage/toolbar/sortByButton/MSortByPopup.qml
similarity index 100%
rename from src/presentation/homePage/toolbar/sortByButton/MSortByPopup.qml
rename to src/presentation/desktop/homePage/toolbar/sortByButton/MSortByPopup.qml
diff --git a/src/presentation/homePage/toolbar/tagSelector/MTagSelectorButton.qml b/src/presentation/desktop/homePage/toolbar/tagSelector/MTagSelectorButton.qml
similarity index 100%
rename from src/presentation/homePage/toolbar/tagSelector/MTagSelectorButton.qml
rename to src/presentation/desktop/homePage/toolbar/tagSelector/MTagSelectorButton.qml
diff --git a/src/presentation/homePage/toolbar/tagSelector/MTagSelectorPopup.qml b/src/presentation/desktop/homePage/toolbar/tagSelector/MTagSelectorPopup.qml
similarity index 100%
rename from src/presentation/homePage/toolbar/tagSelector/MTagSelectorPopup.qml
rename to src/presentation/desktop/homePage/toolbar/tagSelector/MTagSelectorPopup.qml
diff --git a/src/presentation/loginPage/MLoginPage.qml b/src/presentation/desktop/loginPage/MLoginPage.qml
similarity index 100%
rename from src/presentation/loginPage/MLoginPage.qml
rename to src/presentation/desktop/loginPage/MLoginPage.qml
diff --git a/src/presentation/main.qml b/src/presentation/desktop/main.qml
similarity index 100%
rename from src/presentation/main.qml
rename to src/presentation/desktop/main.qml
diff --git a/src/presentation/modules/CppElements/key_sequence_recorder.cpp b/src/presentation/desktop/modules/CppElements/key_sequence_recorder.cpp
similarity index 100%
rename from src/presentation/modules/CppElements/key_sequence_recorder.cpp
rename to src/presentation/desktop/modules/CppElements/key_sequence_recorder.cpp
diff --git a/src/presentation/modules/CppElements/key_sequence_recorder.hpp b/src/presentation/desktop/modules/CppElements/key_sequence_recorder.hpp
similarity index 100%
rename from src/presentation/modules/CppElements/key_sequence_recorder.hpp
rename to src/presentation/desktop/modules/CppElements/key_sequence_recorder.hpp
diff --git a/src/presentation/modules/CppElements/page_view.cpp b/src/presentation/desktop/modules/CppElements/page_view.cpp
similarity index 100%
rename from src/presentation/modules/CppElements/page_view.cpp
rename to src/presentation/desktop/modules/CppElements/page_view.cpp
diff --git a/src/presentation/modules/CppElements/page_view.hpp b/src/presentation/desktop/modules/CppElements/page_view.hpp
similarity index 100%
rename from src/presentation/modules/CppElements/page_view.hpp
rename to src/presentation/desktop/modules/CppElements/page_view.hpp
diff --git a/src/presentation/modules/CppElements/presentation_export.hpp b/src/presentation/desktop/modules/CppElements/presentation_export.hpp
similarity index 100%
rename from src/presentation/modules/CppElements/presentation_export.hpp
rename to src/presentation/desktop/modules/CppElements/presentation_export.hpp
diff --git a/src/presentation/modules/CustomComponents/MAlertBox.qml b/src/presentation/desktop/modules/CustomComponents/MAlertBox.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/MAlertBox.qml
rename to src/presentation/desktop/modules/CustomComponents/MAlertBox.qml
diff --git a/src/presentation/modules/CustomComponents/MBaseListItem.qml b/src/presentation/desktop/modules/CustomComponents/MBaseListItem.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/MBaseListItem.qml
rename to src/presentation/desktop/modules/CustomComponents/MBaseListItem.qml
diff --git a/src/presentation/modules/CustomComponents/MCheckBox.qml b/src/presentation/desktop/modules/CustomComponents/MCheckBox.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/MCheckBox.qml
rename to src/presentation/desktop/modules/CustomComponents/MCheckBox.qml
diff --git a/src/presentation/modules/CustomComponents/MConfirmAccountDeletionPopup.qml b/src/presentation/desktop/modules/CustomComponents/MConfirmAccountDeletionPopup.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/MConfirmAccountDeletionPopup.qml
rename to src/presentation/desktop/modules/CustomComponents/MConfirmAccountDeletionPopup.qml
diff --git a/src/presentation/modules/CustomComponents/MDualToggle.qml b/src/presentation/desktop/modules/CustomComponents/MDualToggle.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/MDualToggle.qml
rename to src/presentation/desktop/modules/CustomComponents/MDualToggle.qml
diff --git a/src/presentation/modules/CustomComponents/MFeedbackPopup.qml b/src/presentation/desktop/modules/CustomComponents/MFeedbackPopup.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/MFeedbackPopup.qml
rename to src/presentation/desktop/modules/CustomComponents/MFeedbackPopup.qml
diff --git a/src/presentation/modules/CustomComponents/MFlickWrapper.qml b/src/presentation/desktop/modules/CustomComponents/MFlickWrapper.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/MFlickWrapper.qml
rename to src/presentation/desktop/modules/CustomComponents/MFlickWrapper.qml
diff --git a/src/presentation/modules/CustomComponents/MLabeledCheckBox.qml b/src/presentation/desktop/modules/CustomComponents/MLabeledCheckBox.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/MLabeledCheckBox.qml
rename to src/presentation/desktop/modules/CustomComponents/MLabeledCheckBox.qml
diff --git a/src/presentation/modules/CustomComponents/MLabeledInputBox.qml b/src/presentation/desktop/modules/CustomComponents/MLabeledInputBox.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/MLabeledInputBox.qml
rename to src/presentation/desktop/modules/CustomComponents/MLabeledInputBox.qml
diff --git a/src/presentation/modules/CustomComponents/MLanguageModel.qml b/src/presentation/desktop/modules/CustomComponents/MLanguageModel.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/MLanguageModel.qml
rename to src/presentation/desktop/modules/CustomComponents/MLanguageModel.qml
diff --git a/src/presentation/modules/CustomComponents/MLogo.qml b/src/presentation/desktop/modules/CustomComponents/MLogo.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/MLogo.qml
rename to src/presentation/desktop/modules/CustomComponents/MLogo.qml
diff --git a/src/presentation/modules/CustomComponents/MOnOffToggle.qml b/src/presentation/desktop/modules/CustomComponents/MOnOffToggle.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/MOnOffToggle.qml
rename to src/presentation/desktop/modules/CustomComponents/MOnOffToggle.qml
diff --git a/src/presentation/modules/CustomComponents/MPageCleanup.qml b/src/presentation/desktop/modules/CustomComponents/MPageCleanup.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/MPageCleanup.qml
rename to src/presentation/desktop/modules/CustomComponents/MPageCleanup.qml
diff --git a/src/presentation/modules/CustomComponents/MProgressArc.qml b/src/presentation/desktop/modules/CustomComponents/MProgressArc.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/MProgressArc.qml
rename to src/presentation/desktop/modules/CustomComponents/MProgressArc.qml
diff --git a/src/presentation/modules/CustomComponents/MProgressBar.qml b/src/presentation/desktop/modules/CustomComponents/MProgressBar.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/MProgressBar.qml
rename to src/presentation/desktop/modules/CustomComponents/MProgressBar.qml
diff --git a/src/presentation/modules/CustomComponents/MRadioButton.qml b/src/presentation/desktop/modules/CustomComponents/MRadioButton.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/MRadioButton.qml
rename to src/presentation/desktop/modules/CustomComponents/MRadioButton.qml
diff --git a/src/presentation/modules/CustomComponents/MRadioButtonSelector.qml b/src/presentation/desktop/modules/CustomComponents/MRadioButtonSelector.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/MRadioButtonSelector.qml
rename to src/presentation/desktop/modules/CustomComponents/MRadioButtonSelector.qml
diff --git a/src/presentation/modules/CustomComponents/MSlider.qml b/src/presentation/desktop/modules/CustomComponents/MSlider.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/MSlider.qml
rename to src/presentation/desktop/modules/CustomComponents/MSlider.qml
diff --git a/src/presentation/modules/CustomComponents/MSpinbox.qml b/src/presentation/desktop/modules/CustomComponents/MSpinbox.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/MSpinbox.qml
rename to src/presentation/desktop/modules/CustomComponents/MSpinbox.qml
diff --git a/src/presentation/modules/CustomComponents/MSpinner.qml b/src/presentation/desktop/modules/CustomComponents/MSpinner.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/MSpinner.qml
rename to src/presentation/desktop/modules/CustomComponents/MSpinner.qml
diff --git a/src/presentation/modules/CustomComponents/MTitle.qml b/src/presentation/desktop/modules/CustomComponents/MTitle.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/MTitle.qml
rename to src/presentation/desktop/modules/CustomComponents/MTitle.qml
diff --git a/src/presentation/modules/CustomComponents/MToolTip.qml b/src/presentation/desktop/modules/CustomComponents/MToolTip.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/MToolTip.qml
rename to src/presentation/desktop/modules/CustomComponents/MToolTip.qml
diff --git a/src/presentation/modules/CustomComponents/MWarningPopup.qml b/src/presentation/desktop/modules/CustomComponents/MWarningPopup.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/MWarningPopup.qml
rename to src/presentation/desktop/modules/CustomComponents/MWarningPopup.qml
diff --git a/src/presentation/modules/CustomComponents/MWrappedCheckBox.qml b/src/presentation/desktop/modules/CustomComponents/MWrappedCheckBox.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/MWrappedCheckBox.qml
rename to src/presentation/desktop/modules/CustomComponents/MWrappedCheckBox.qml
diff --git a/src/presentation/modules/CustomComponents/bookSelector/MBookSelector.qml b/src/presentation/desktop/modules/CustomComponents/bookSelector/MBookSelector.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/bookSelector/MBookSelector.qml
rename to src/presentation/desktop/modules/CustomComponents/bookSelector/MBookSelector.qml
diff --git a/src/presentation/modules/CustomComponents/bookSelector/MBookSelectorPopup.qml b/src/presentation/desktop/modules/CustomComponents/bookSelector/MBookSelectorPopup.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/bookSelector/MBookSelectorPopup.qml
rename to src/presentation/desktop/modules/CustomComponents/bookSelector/MBookSelectorPopup.qml
diff --git a/src/presentation/modules/CustomComponents/buttons/MButton.qml b/src/presentation/desktop/modules/CustomComponents/buttons/MButton.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/buttons/MButton.qml
rename to src/presentation/desktop/modules/CustomComponents/buttons/MButton.qml
diff --git a/src/presentation/modules/CustomComponents/buttons/MRemoveOptionButton.qml b/src/presentation/desktop/modules/CustomComponents/buttons/MRemoveOptionButton.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/buttons/MRemoveOptionButton.qml
rename to src/presentation/desktop/modules/CustomComponents/buttons/MRemoveOptionButton.qml
diff --git a/src/presentation/modules/CustomComponents/buttons/MSearchButton.qml b/src/presentation/desktop/modules/CustomComponents/buttons/MSearchButton.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/buttons/MSearchButton.qml
rename to src/presentation/desktop/modules/CustomComponents/buttons/MSearchButton.qml
diff --git a/src/presentation/modules/CustomComponents/comboBox/ComboBoxLogic.js b/src/presentation/desktop/modules/CustomComponents/comboBox/ComboBoxLogic.js
similarity index 100%
rename from src/presentation/modules/CustomComponents/comboBox/ComboBoxLogic.js
rename to src/presentation/desktop/modules/CustomComponents/comboBox/ComboBoxLogic.js
diff --git a/src/presentation/modules/CustomComponents/comboBox/MComboBox.qml b/src/presentation/desktop/modules/CustomComponents/comboBox/MComboBox.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/comboBox/MComboBox.qml
rename to src/presentation/desktop/modules/CustomComponents/comboBox/MComboBox.qml
diff --git a/src/presentation/modules/CustomComponents/comboBox/MComboBoxPopup.qml b/src/presentation/desktop/modules/CustomComponents/comboBox/MComboBoxPopup.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/comboBox/MComboBoxPopup.qml
rename to src/presentation/desktop/modules/CustomComponents/comboBox/MComboBoxPopup.qml
diff --git a/src/presentation/modules/CustomComponents/qmldir b/src/presentation/desktop/modules/CustomComponents/qmldir
similarity index 100%
rename from src/presentation/modules/CustomComponents/qmldir
rename to src/presentation/desktop/modules/CustomComponents/qmldir
diff --git a/src/presentation/modules/CustomComponents/rightClickMenu/MRightClickMenu.qml b/src/presentation/desktop/modules/CustomComponents/rightClickMenu/MRightClickMenu.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/rightClickMenu/MRightClickMenu.qml
rename to src/presentation/desktop/modules/CustomComponents/rightClickMenu/MRightClickMenu.qml
diff --git a/src/presentation/modules/CustomComponents/rightClickMenu/MRightClickMenuItem.qml b/src/presentation/desktop/modules/CustomComponents/rightClickMenu/MRightClickMenuItem.qml
similarity index 100%
rename from src/presentation/modules/CustomComponents/rightClickMenu/MRightClickMenuItem.qml
rename to src/presentation/desktop/modules/CustomComponents/rightClickMenu/MRightClickMenuItem.qml
diff --git a/src/presentation/modules/CustomComponents/rightClickMenu/PopupPositionLogic.js b/src/presentation/desktop/modules/CustomComponents/rightClickMenu/PopupPositionLogic.js
similarity index 100%
rename from src/presentation/modules/CustomComponents/rightClickMenu/PopupPositionLogic.js
rename to src/presentation/desktop/modules/CustomComponents/rightClickMenu/PopupPositionLogic.js
diff --git a/src/presentation/qmlSources.qrc b/src/presentation/desktop/qmlSourcesDesktop.qrc
similarity index 97%
rename from src/presentation/qmlSources.qrc
rename to src/presentation/desktop/qmlSourcesDesktop.qrc
index fb14ad156..7cc472e1c 100644
--- a/src/presentation/qmlSources.qrc
+++ b/src/presentation/desktop/qmlSourcesDesktop.qrc
@@ -14,9 +14,6 @@
sidebar/MProfilePopupItem.qml
sidebar/MSidebar.qml
sidebar/MSidebarItem.qml
- StyleSheet.qml
- IconSheet.qml
- FontSheet.qml
modules/CustomComponents/buttons/MButton.qml
modules/CustomComponents/buttons/MRemoveOptionButton.qml
modules/CustomComponents/buttons/MSearchButton.qml
@@ -65,7 +62,6 @@
settings/shortcutsPage/MRecordKeyBox.qml
settings/accountPage/MAccountPage.qml
settings/accountPage/MSelectProfilePictureArea.qml
- Globals.qml
modules/CustomComponents/comboBox/ComboBoxLogic.js
modules/CustomComponents/MFlickWrapper.qml
settings/MBehaviorPage.qml
@@ -97,7 +93,6 @@
readingPage/MDictionaryPopup.qml
readingPage/MBookmarkItem.qml
readingPage/MExplanationPopup.qml
- TranslationsModel.qml
homePage/MBookMultiSelectRightClickPopup.qml
modules/CustomComponents/MLanguageModel.qml
homePage/folderSidebar/MFolderSidebarItem.qml
@@ -122,7 +117,6 @@
toolsPage/MExtractPagesPopup.qml
toolsPage/MImageToPdfPopup.qml
toolsPage/MPdfToImagePopup.qml
- GlobalSettings.qml
modules/CustomComponents/MFeedbackPopup.qml
diff --git a/src/presentation/readingPage/DocumentNavigation.js b/src/presentation/desktop/readingPage/DocumentNavigation.js
similarity index 100%
rename from src/presentation/readingPage/DocumentNavigation.js
rename to src/presentation/desktop/readingPage/DocumentNavigation.js
diff --git a/src/presentation/readingPage/MBookmarkItem.qml b/src/presentation/desktop/readingPage/MBookmarkItem.qml
similarity index 100%
rename from src/presentation/readingPage/MBookmarkItem.qml
rename to src/presentation/desktop/readingPage/MBookmarkItem.qml
diff --git a/src/presentation/readingPage/MBookmarksSidebar.qml b/src/presentation/desktop/readingPage/MBookmarksSidebar.qml
similarity index 100%
rename from src/presentation/readingPage/MBookmarksSidebar.qml
rename to src/presentation/desktop/readingPage/MBookmarksSidebar.qml
diff --git a/src/presentation/readingPage/MChapterSidebar.qml b/src/presentation/desktop/readingPage/MChapterSidebar.qml
similarity index 100%
rename from src/presentation/readingPage/MChapterSidebar.qml
rename to src/presentation/desktop/readingPage/MChapterSidebar.qml
diff --git a/src/presentation/readingPage/MDictionaryPopup.qml b/src/presentation/desktop/readingPage/MDictionaryPopup.qml
similarity index 100%
rename from src/presentation/readingPage/MDictionaryPopup.qml
rename to src/presentation/desktop/readingPage/MDictionaryPopup.qml
diff --git a/src/presentation/readingPage/MDocumentView.qml b/src/presentation/desktop/readingPage/MDocumentView.qml
similarity index 100%
rename from src/presentation/readingPage/MDocumentView.qml
rename to src/presentation/desktop/readingPage/MDocumentView.qml
diff --git a/src/presentation/readingPage/MExplanationPopup.qml b/src/presentation/desktop/readingPage/MExplanationPopup.qml
similarity index 100%
rename from src/presentation/readingPage/MExplanationPopup.qml
rename to src/presentation/desktop/readingPage/MExplanationPopup.qml
diff --git a/src/presentation/readingPage/MExternalReadingPage.qml b/src/presentation/desktop/readingPage/MExternalReadingPage.qml
similarity index 100%
rename from src/presentation/readingPage/MExternalReadingPage.qml
rename to src/presentation/desktop/readingPage/MExternalReadingPage.qml
diff --git a/src/presentation/readingPage/MReadingPage.qml b/src/presentation/desktop/readingPage/MReadingPage.qml
similarity index 100%
rename from src/presentation/readingPage/MReadingPage.qml
rename to src/presentation/desktop/readingPage/MReadingPage.qml
diff --git a/src/presentation/readingPage/MSelectionOptionsPopup.qml b/src/presentation/desktop/readingPage/MSelectionOptionsPopup.qml
similarity index 100%
rename from src/presentation/readingPage/MSelectionOptionsPopup.qml
rename to src/presentation/desktop/readingPage/MSelectionOptionsPopup.qml
diff --git a/src/presentation/readingPage/externalReadingToolbar/MExternalReadingOptionsPopup.qml b/src/presentation/desktop/readingPage/externalReadingToolbar/MExternalReadingOptionsPopup.qml
similarity index 100%
rename from src/presentation/readingPage/externalReadingToolbar/MExternalReadingOptionsPopup.qml
rename to src/presentation/desktop/readingPage/externalReadingToolbar/MExternalReadingOptionsPopup.qml
diff --git a/src/presentation/readingPage/externalReadingToolbar/MExternalReadingToolBar.qml b/src/presentation/desktop/readingPage/externalReadingToolbar/MExternalReadingToolBar.qml
similarity index 100%
rename from src/presentation/readingPage/externalReadingToolbar/MExternalReadingToolBar.qml
rename to src/presentation/desktop/readingPage/externalReadingToolbar/MExternalReadingToolBar.qml
diff --git a/src/presentation/readingPage/readingSearchbar/MReadingSearchbar.qml b/src/presentation/desktop/readingPage/readingSearchbar/MReadingSearchbar.qml
similarity index 100%
rename from src/presentation/readingPage/readingSearchbar/MReadingSearchbar.qml
rename to src/presentation/desktop/readingPage/readingSearchbar/MReadingSearchbar.qml
diff --git a/src/presentation/readingPage/readingSearchbar/MReadingSearchbarOptionsPopup.qml b/src/presentation/desktop/readingPage/readingSearchbar/MReadingSearchbarOptionsPopup.qml
similarity index 100%
rename from src/presentation/readingPage/readingSearchbar/MReadingSearchbarOptionsPopup.qml
rename to src/presentation/desktop/readingPage/readingSearchbar/MReadingSearchbarOptionsPopup.qml
diff --git a/src/presentation/readingPage/readingToolbar/MReadingOptionsPopup.qml b/src/presentation/desktop/readingPage/readingToolbar/MReadingOptionsPopup.qml
similarity index 100%
rename from src/presentation/readingPage/readingToolbar/MReadingOptionsPopup.qml
rename to src/presentation/desktop/readingPage/readingToolbar/MReadingOptionsPopup.qml
diff --git a/src/presentation/readingPage/readingToolbar/MReadingToolBar.qml b/src/presentation/desktop/readingPage/readingToolbar/MReadingToolBar.qml
similarity index 100%
rename from src/presentation/readingPage/readingToolbar/MReadingToolBar.qml
rename to src/presentation/desktop/readingPage/readingToolbar/MReadingToolBar.qml
diff --git a/src/presentation/registerPage/MAcceptPolicy.qml b/src/presentation/desktop/registerPage/MAcceptPolicy.qml
similarity index 100%
rename from src/presentation/registerPage/MAcceptPolicy.qml
rename to src/presentation/desktop/registerPage/MAcceptPolicy.qml
diff --git a/src/presentation/registerPage/MRegisterPage.qml b/src/presentation/desktop/registerPage/MRegisterPage.qml
similarity index 100%
rename from src/presentation/registerPage/MRegisterPage.qml
rename to src/presentation/desktop/registerPage/MRegisterPage.qml
diff --git a/src/presentation/settings/MAboutPage.qml b/src/presentation/desktop/settings/MAboutPage.qml
similarity index 100%
rename from src/presentation/settings/MAboutPage.qml
rename to src/presentation/desktop/settings/MAboutPage.qml
diff --git a/src/presentation/settings/MAppearancePage.qml b/src/presentation/desktop/settings/MAppearancePage.qml
similarity index 100%
rename from src/presentation/settings/MAppearancePage.qml
rename to src/presentation/desktop/settings/MAppearancePage.qml
diff --git a/src/presentation/settings/MBehaviorPage.qml b/src/presentation/desktop/settings/MBehaviorPage.qml
similarity index 100%
rename from src/presentation/settings/MBehaviorPage.qml
rename to src/presentation/desktop/settings/MBehaviorPage.qml
diff --git a/src/presentation/settings/MSettings.qml b/src/presentation/desktop/settings/MSettings.qml
similarity index 100%
rename from src/presentation/settings/MSettings.qml
rename to src/presentation/desktop/settings/MSettings.qml
diff --git a/src/presentation/settings/MStoragePage.qml b/src/presentation/desktop/settings/MStoragePage.qml
similarity index 100%
rename from src/presentation/settings/MStoragePage.qml
rename to src/presentation/desktop/settings/MStoragePage.qml
diff --git a/src/presentation/settings/MSupportUsPage.qml b/src/presentation/desktop/settings/MSupportUsPage.qml
similarity index 100%
rename from src/presentation/settings/MSupportUsPage.qml
rename to src/presentation/desktop/settings/MSupportUsPage.qml
diff --git a/src/presentation/settings/accountPage/MAccountPage.qml b/src/presentation/desktop/settings/accountPage/MAccountPage.qml
similarity index 100%
rename from src/presentation/settings/accountPage/MAccountPage.qml
rename to src/presentation/desktop/settings/accountPage/MAccountPage.qml
diff --git a/src/presentation/settings/accountPage/MSelectProfilePictureArea.qml b/src/presentation/desktop/settings/accountPage/MSelectProfilePictureArea.qml
similarity index 100%
rename from src/presentation/settings/accountPage/MSelectProfilePictureArea.qml
rename to src/presentation/desktop/settings/accountPage/MSelectProfilePictureArea.qml
diff --git a/src/presentation/settings/settingsSidebar/MSettingsSidebar.qml b/src/presentation/desktop/settings/settingsSidebar/MSettingsSidebar.qml
similarity index 100%
rename from src/presentation/settings/settingsSidebar/MSettingsSidebar.qml
rename to src/presentation/desktop/settings/settingsSidebar/MSettingsSidebar.qml
diff --git a/src/presentation/settings/settingsSidebar/MSettingsSidebarItem.qml b/src/presentation/desktop/settings/settingsSidebar/MSettingsSidebarItem.qml
similarity index 100%
rename from src/presentation/settings/settingsSidebar/MSettingsSidebarItem.qml
rename to src/presentation/desktop/settings/settingsSidebar/MSettingsSidebarItem.qml
diff --git a/src/presentation/settings/shortcutsPage/MAddShortcutPopup.qml b/src/presentation/desktop/settings/shortcutsPage/MAddShortcutPopup.qml
similarity index 100%
rename from src/presentation/settings/shortcutsPage/MAddShortcutPopup.qml
rename to src/presentation/desktop/settings/shortcutsPage/MAddShortcutPopup.qml
diff --git a/src/presentation/settings/shortcutsPage/MRecordKeyBox.qml b/src/presentation/desktop/settings/shortcutsPage/MRecordKeyBox.qml
similarity index 100%
rename from src/presentation/settings/shortcutsPage/MRecordKeyBox.qml
rename to src/presentation/desktop/settings/shortcutsPage/MRecordKeyBox.qml
diff --git a/src/presentation/settings/shortcutsPage/MShortcutDelegate.qml b/src/presentation/desktop/settings/shortcutsPage/MShortcutDelegate.qml
similarity index 100%
rename from src/presentation/settings/shortcutsPage/MShortcutDelegate.qml
rename to src/presentation/desktop/settings/shortcutsPage/MShortcutDelegate.qml
diff --git a/src/presentation/settings/shortcutsPage/MShortcutsPage.qml b/src/presentation/desktop/settings/shortcutsPage/MShortcutsPage.qml
similarity index 100%
rename from src/presentation/settings/shortcutsPage/MShortcutsPage.qml
rename to src/presentation/desktop/settings/shortcutsPage/MShortcutsPage.qml
diff --git a/src/presentation/settings/updatesPage/MUpToDate.qml b/src/presentation/desktop/settings/updatesPage/MUpToDate.qml
similarity index 100%
rename from src/presentation/settings/updatesPage/MUpToDate.qml
rename to src/presentation/desktop/settings/updatesPage/MUpToDate.qml
diff --git a/src/presentation/settings/updatesPage/MUpdatesAvailable.qml b/src/presentation/desktop/settings/updatesPage/MUpdatesAvailable.qml
similarity index 100%
rename from src/presentation/settings/updatesPage/MUpdatesAvailable.qml
rename to src/presentation/desktop/settings/updatesPage/MUpdatesAvailable.qml
diff --git a/src/presentation/settings/updatesPage/MUpdatesPage.qml b/src/presentation/desktop/settings/updatesPage/MUpdatesPage.qml
similarity index 100%
rename from src/presentation/settings/updatesPage/MUpdatesPage.qml
rename to src/presentation/desktop/settings/updatesPage/MUpdatesPage.qml
diff --git a/src/presentation/settings/updatesPage/MWindowsUpdatingPopup.qml b/src/presentation/desktop/settings/updatesPage/MWindowsUpdatingPopup.qml
similarity index 100%
rename from src/presentation/settings/updatesPage/MWindowsUpdatingPopup.qml
rename to src/presentation/desktop/settings/updatesPage/MWindowsUpdatingPopup.qml
diff --git a/src/presentation/sidebar/MProfileBox.qml b/src/presentation/desktop/sidebar/MProfileBox.qml
similarity index 100%
rename from src/presentation/sidebar/MProfileBox.qml
rename to src/presentation/desktop/sidebar/MProfileBox.qml
diff --git a/src/presentation/sidebar/MProfilePopup.qml b/src/presentation/desktop/sidebar/MProfilePopup.qml
similarity index 100%
rename from src/presentation/sidebar/MProfilePopup.qml
rename to src/presentation/desktop/sidebar/MProfilePopup.qml
diff --git a/src/presentation/sidebar/MProfilePopupItem.qml b/src/presentation/desktop/sidebar/MProfilePopupItem.qml
similarity index 100%
rename from src/presentation/sidebar/MProfilePopupItem.qml
rename to src/presentation/desktop/sidebar/MProfilePopupItem.qml
diff --git a/src/presentation/sidebar/MSidebar.qml b/src/presentation/desktop/sidebar/MSidebar.qml
similarity index 97%
rename from src/presentation/sidebar/MSidebar.qml
rename to src/presentation/desktop/sidebar/MSidebar.qml
index efda8572b..1cc24fb24 100644
--- a/src/presentation/sidebar/MSidebar.qml
+++ b/src/presentation/desktop/sidebar/MSidebar.qml
@@ -28,22 +28,27 @@ Item {
Shortcut {
sequence: "Ctrl+1"
onActivated: loadPage(freeBooksPage, root.freeBooksItem)
+ enabled: root.visible
}
Shortcut {
sequence: "Ctrl+2"
onActivated: loadPage(homePage, root.homeItem)
+ enabled: root.visible
}
Shortcut {
sequence: "Ctrl+3"
onActivated: loadPage(statisticsPage, root.statisticsItem)
+ enabled: root.visible
}
Shortcut {
sequence: "Ctrl+4"
onActivated: loadPage(toolsPage, root.toolsItem)
+ enabled: root.visible
}
Shortcut {
sequence: "Ctrl+5"
onActivated: loadPage(settings, root.settingsItem)
+ enabled: root.visible
}
Pane {
diff --git a/src/presentation/sidebar/MSidebarItem.qml b/src/presentation/desktop/sidebar/MSidebarItem.qml
similarity index 100%
rename from src/presentation/sidebar/MSidebarItem.qml
rename to src/presentation/desktop/sidebar/MSidebarItem.qml
diff --git a/src/presentation/statisticsPage/MStatisticsPage.qml b/src/presentation/desktop/statisticsPage/MStatisticsPage.qml
similarity index 100%
rename from src/presentation/statisticsPage/MStatisticsPage.qml
rename to src/presentation/desktop/statisticsPage/MStatisticsPage.qml
diff --git a/src/presentation/toolsPage/MExtractPagesPopup.qml b/src/presentation/desktop/toolsPage/MExtractPagesPopup.qml
similarity index 100%
rename from src/presentation/toolsPage/MExtractPagesPopup.qml
rename to src/presentation/desktop/toolsPage/MExtractPagesPopup.qml
diff --git a/src/presentation/toolsPage/MImageToPdfPopup.qml b/src/presentation/desktop/toolsPage/MImageToPdfPopup.qml
similarity index 100%
rename from src/presentation/toolsPage/MImageToPdfPopup.qml
rename to src/presentation/desktop/toolsPage/MImageToPdfPopup.qml
diff --git a/src/presentation/toolsPage/MMergePopup.qml b/src/presentation/desktop/toolsPage/MMergePopup.qml
similarity index 100%
rename from src/presentation/toolsPage/MMergePopup.qml
rename to src/presentation/desktop/toolsPage/MMergePopup.qml
diff --git a/src/presentation/toolsPage/MPdfToImagePopup.qml b/src/presentation/desktop/toolsPage/MPdfToImagePopup.qml
similarity index 100%
rename from src/presentation/toolsPage/MPdfToImagePopup.qml
rename to src/presentation/desktop/toolsPage/MPdfToImagePopup.qml
diff --git a/src/presentation/toolsPage/MToolsPage.qml b/src/presentation/desktop/toolsPage/MToolsPage.qml
similarity index 100%
rename from src/presentation/toolsPage/MToolsPage.qml
rename to src/presentation/desktop/toolsPage/MToolsPage.qml
diff --git a/src/presentation/feature/arabic-translation b/src/presentation/feature/arabic-translation
deleted file mode 100644
index 1f068c5de..000000000
--- a/src/presentation/feature/arabic-translation
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/src/presentation/mobile/explorePage/MExplorePage.qml b/src/presentation/mobile/explorePage/MExplorePage.qml
new file mode 100644
index 000000000..9f97ceedc
--- /dev/null
+++ b/src/presentation/mobile/explorePage/MExplorePage.qml
@@ -0,0 +1,24 @@
+import QtQuick
+import QtQuick.Controls.Material
+import QtQuick.Layouts
+import Librum.style
+import Librum.fonts
+import Librum.controllers
+import CustomComponents
+
+Page {
+ id: root
+ horizontalPadding: 18
+ background: Rectangle {
+ color: Style.colorPageBackground
+ }
+
+ Label {
+ id: welcomeText
+ anchors.centerIn: parent
+ text: qsTr("Explore Page")
+ color: Style.colorTitle
+ font.weight: Font.Bold
+ font.pointSize: Fonts.size28
+ }
+}
diff --git a/src/presentation/mobile/libraryPage/MBook.qml b/src/presentation/mobile/libraryPage/MBook.qml
new file mode 100644
index 000000000..5aa97515d
--- /dev/null
+++ b/src/presentation/mobile/libraryPage/MBook.qml
@@ -0,0 +1,105 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Librum.style
+import Librum.icons
+import CustomComponents
+import Librum.fonts
+import Librum.globals
+
+Item {
+ id: root
+ signal clicked
+
+ implicitWidth: 120
+ implicitHeight: 190
+ clip: true
+
+ ColumnLayout {
+ id: layout
+ anchors.fill: parent
+ spacing: 0
+
+ Rectangle {
+ id: upperBookPart
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ color: Style.colorBookImageBackground
+
+ Image {
+ id: downloadBookIcon
+ anchors.centerIn: parent
+ visible: !model.downloaded
+ sourceSize.width: 32
+ fillMode: Image.PreserveAspectFit
+ source: Icons.downloadSelected
+ opacity: 1
+ z: 3
+ }
+
+ ColumnLayout {
+ id: upperPartLayout
+ anchors.centerIn: parent
+ spacing: 0
+
+ Image {
+ id: bookCover
+ // visible: source != ""
+ visible: false
+ Layout.alignment: Qt.AlignHCenter
+ Layout.topMargin: -10
+ // source: cover
+ cache: false
+ }
+ }
+ }
+
+ Rectangle {
+ id: lowerBookPart
+ Layout.fillWidth: true
+ Layout.preferredHeight: 42
+ color: "transparent"
+
+ ColumnLayout {
+ id: bottomPartLayout
+ width: parent.width
+ anchors.horizontalCenter: parent.horizontalCenter
+ spacing: 0
+
+ Label {
+ id: title
+ Layout.fillWidth: true
+ Layout.preferredHeight: 24
+ Layout.topMargin: 5
+ clip: true
+ text: model.title === "" ? qsTr("Unknown") : model.title
+ font.weight: Font.Medium
+ verticalAlignment: Text.AlignVCenter
+ color: Style.colorTitle
+ font.pointSize: Fonts.size12
+ lineHeight: 0.8
+ wrapMode: TextInput.WrapAtWordBoundaryOrAnywhere
+ elide: Text.ElideRight
+ }
+
+ Label {
+ id: authors
+ Layout.fillWidth: true
+ clip: true
+ text: model.authors === "" ? qsTr("Unknown") : model.authors
+ color: Style.colorLightText
+ font.pointSize: Fonts.size11
+ elide: Text.ElideRight
+ }
+ }
+ }
+ }
+
+ TapHandler {
+ onTapped: root.clicked()
+ }
+
+ function giveFocus() {
+ root.forceActiveFocus()
+ }
+}
diff --git a/src/presentation/mobile/libraryPage/MLibraryPage.qml b/src/presentation/mobile/libraryPage/MLibraryPage.qml
new file mode 100644
index 000000000..d6223daa3
--- /dev/null
+++ b/src/presentation/mobile/libraryPage/MLibraryPage.qml
@@ -0,0 +1,101 @@
+import QtQuick
+import QtQuick.Controls.Material
+import QtQuick.Layouts
+import Librum.style
+import Librum.fonts
+import Librum.controllers
+import Librum.globals
+import CustomComponents
+
+Page {
+ id: root
+ horizontalPadding: 18
+ background: Rectangle {
+ color: Style.colorPageBackground
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+
+ Pane {
+ id: bookGridContainer
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.minimumHeight: 100
+ padding: 0
+ background: Rectangle {
+ color: "transparent"
+ }
+
+ GridView {
+ id: bookGrid
+ width: cellWidth * 2
+ height: parent.height
+ anchors.horizontalCenter: parent.horizontalCenter
+ cellWidth: 120 + 14
+ cellHeight: 190 + 14
+ rightMargin: -14
+ layoutDirection: Qt.LeftToRight
+ LayoutMirroring.enabled: false
+ LayoutMirroring.childrenInherit: true
+ interactive: true
+ boundsBehavior: Flickable.StopAtBounds
+ flickDeceleration: 1650
+ maximumFlickVelocity: 2500
+ clip: true
+ model: LibraryController.libraryModel
+ delegate: MBook {
+ id: bookDelegate
+
+ onClicked: {
+ if (model.downloaded) {
+ Globals.selectedBook = LibraryController.getBook(
+ model.uuid)
+ BookController.setUp(Globals.selectedBook.uuid)
+
+ LibraryController.refreshLastOpenedFlag(
+ Globals.selectedBook.uuid)
+
+ loadPage(readingPage)
+ } else {
+ LibraryController.downloadBookMedia(model.uuid)
+ }
+ }
+ }
+ }
+
+ ScrollBar {
+ id: verticalScrollbar
+ width: pressed ? 14 : 12
+ hoverEnabled: true
+ active: true
+ policy: ScrollBar.AlwaysOff
+ visible: bookGrid.contentHeight > bookGrid.height
+ orientation: Qt.Vertical
+ size: bookGrid.height / bookGrid.contentHeight
+ minimumSize: 0.04
+ position: (bookGrid.contentY - bookGrid.originY) / bookGrid.contentHeight
+ onPositionChanged: if (pressed)
+ bookGrid.contentY = position
+ * bookGrid.contentHeight + bookGrid.originY
+ anchors.top: parent.top
+ anchors.right: parent.right
+ anchors.bottomMargin: 16
+ anchors.bottom: parent.bottom
+ horizontalPadding: 4
+
+ contentItem: Rectangle {
+ color: Style.colorScrollBarHandle
+ opacity: verticalScrollbar.pressed ? 0.8 : 1
+ radius: 4
+ }
+
+ background: Rectangle {
+ implicitWidth: 26
+ implicitHeight: 200
+ color: "transparent"
+ }
+ }
+ }
+ }
+}
diff --git a/src/presentation/mobile/loginPage/MLoginPage.qml b/src/presentation/mobile/loginPage/MLoginPage.qml
new file mode 100644
index 000000000..4f615978b
--- /dev/null
+++ b/src/presentation/mobile/loginPage/MLoginPage.qml
@@ -0,0 +1,215 @@
+import QtQuick
+import QtQuick.Controls.Material
+import QtQuick.Layouts
+import Librum.style
+import Librum.fonts
+import Librum.controllers
+import CustomComponents
+
+Page {
+ id: root
+ horizontalPadding: 18
+ background: Rectangle {
+ color: Style.colorPageBackground
+ }
+
+ Connections {
+ id: proccessLoginResult
+ target: AuthController
+ function onLoginFinished(errorCode, message) {
+ internal.processLoginResult(errorCode, message)
+ }
+ }
+
+ Connections {
+ id: proccessLoadingUserResult
+ target: UserController
+ function onFinishedLoadingUser(success) {
+ if (success)
+ loadPage(homePage, navbar.libraryItem)
+ else
+ loginFailedPopup.open()
+ }
+ }
+
+ ColumnLayout {
+ id: layout
+ anchors.fill: parent
+ spacing: 0
+
+ Item {
+ Layout.fillHeight: true
+ Layout.minimumHeight: 32
+ Layout.maximumHeight: 68
+ }
+
+ MLogo {
+ id: logo
+ Layout.alignment: Qt.AlignHCenter
+ Layout.preferredWidth: 92
+ Layout.preferredHeight: 92
+ }
+
+ Label {
+ id: welcomeText
+ Layout.alignment: Qt.AlignHCenter
+ Layout.topMargin: 52
+ text: qsTr("Welcome back!")
+ color: Style.colorTitle
+ font.weight: Font.Bold
+ font.pointSize: Fonts.size26
+ }
+
+ Label {
+ id: loginText
+ Layout.topMargin: 4
+ Layout.alignment: Qt.AlignHCenter
+ text: qsTr("Log into your account")
+ color: Style.colorSubtitle
+ font.pointSize: Fonts.size13dot25
+ }
+
+ MLabeledInputBox {
+ id: emailInput
+ Layout.fillWidth: true
+ Layout.preferredHeight: 54
+ Layout.topMargin: 40
+ placeHolderText: qsTr("Email")
+ }
+
+ MLabeledInputBox {
+ id: passwordInput
+ Layout.fillWidth: true
+ Layout.preferredHeight: 54
+ Layout.topMargin: 8
+ isPassword: true
+ placeHolderText: qsTr("Password")
+ textHidden: true
+ }
+
+ MButton {
+ id: signInButton
+ Layout.fillWidth: true
+ Layout.preferredHeight: 54
+ Layout.topMargin: 20
+ borderWidth: 0
+ radius: 8
+ backgroundColor: Style.colorBasePurple
+ fontSize: Fonts.size16
+ opacityOnPressed: 0.85
+ textColor: Style.colorFocusedButtonText
+ text: qsTr("Sign in")
+
+ onClicked: internal.login()
+ }
+
+ Label {
+ Layout.topMargin: 16
+ Layout.alignment: Qt.AlignHCenter
+ text: qsTr("Forgot password?")
+ font.pointSize: Fonts.size13dot25
+ font.weight: Font.Medium
+ opacity: forgotPasswordArea.pressed ? 0.8 : 1
+ color: Style.colorBasePurple
+
+ TapHandler {
+ id: forgotPasswordArea
+ onTapped: loadPage(forgotPasswordPage)
+ }
+ }
+
+ Item {
+ Layout.fillHeight: true
+ }
+
+ Item {
+ id: registerLinkContainer
+ Layout.alignment: Qt.AlignHCenter
+ Layout.bottomMargin: 12
+ implicitHeight: registerLinkRow.implicitHeight + 8
+ implicitWidth: registerLinkRow.implicitWidth + 8
+
+ RowLayout {
+ id: registerLinkRow
+ anchors.centerIn: parent
+ spacing: 0
+
+ Label {
+ text: qsTr("Don't have an account? ")
+ font.pointSize: Fonts.size13
+ opacity: registerLinkArea.pressed ? 0.8 : 1
+ font.weight: Font.Medium
+ color: Style.colorText
+ }
+
+ Label {
+ text: qsTr("Register!")
+ font.pointSize: Fonts.size13
+ opacity: registerLinkArea.pressed ? 0.8 : 1
+ font.weight: Font.Medium
+ color: Style.colorBasePurple
+ }
+ }
+
+ TapHandler {
+ id: registerLinkArea
+ onTapped: loadPage(registerPage)
+ }
+ }
+ }
+
+ QtObject {
+ id: internal
+ property color previousBorderColor: emailInput.borderColor
+
+ function login() {
+ AuthController.loginUser(emailInput.text, passwordInput.text, true)
+ }
+
+ function processLoginResult(errorCode, message) {
+ if (errorCode === ErrorCode.NoError) {
+ UserController.loadUser(true)
+ } else {
+ internal.setLoginError(errorCode, message)
+ }
+ }
+
+ function setLoginError(errorCode, message) {
+ switch (errorCode) {
+ case ErrorCode.EmailOrPasswordIsWrong:
+ emailInput.setError()
+ passwordInput.errorText = message
+ passwordInput.setError()
+ break
+ case ErrorCode.EmailAddressTooLong:
+ // Fall through
+ case ErrorCode.EmailAddressTooShort:
+ // Fall through
+ case ErrorCode.InvalidEmailAddressFormat:
+ emailInput.errorText = message
+ emailInput.setError()
+ break
+ case ErrorCode.PasswordTooLong:
+ // Fall through
+ case ErrorCode.PasswordTooShort:
+
+ passwordInput.errorText = message
+ passwordInput.setError()
+ break
+ default:
+ generalErrorText.text = message
+ generalErrorText.visible = true
+ }
+ }
+
+ function clearLoginError() {
+ emailInput.errorText = ""
+ emailInput.clearError()
+ passwordInput.errorText = ""
+ passwordInput.clearError()
+
+ generalErrorText.visible = false
+ generalErrorText.text = ""
+ }
+ }
+}
diff --git a/src/presentation/mobile/main.qml b/src/presentation/mobile/main.qml
new file mode 100644
index 000000000..842538df3
--- /dev/null
+++ b/src/presentation/mobile/main.qml
@@ -0,0 +1,109 @@
+import QtQuick
+import QtQuick.Controls.Material
+import QtQuick.Layouts
+import QtQuick.Window
+import "startPage"
+import "loginPage"
+import "registerPage"
+import "navbar"
+import "libraryPage"
+import "explorePage"
+import "readingPage"
+import "profilePage"
+import "myAccountPage"
+
+ApplicationWindow {
+ id: baseRoot
+ visible: true
+ width: 640
+ height: 480
+ visibility: Window.Maximized
+ title: qsTr("Librum")
+
+ onClosing: {
+ if (stackView.depth > 2) {
+ close.accepted = false
+ let item = stackView.pop()
+ } else {
+ return
+ }
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+ spacing: 0
+
+ StackView {
+ id: stackView
+ property bool pageHasNavbar: false
+
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ initialItem: startPage
+
+ popEnter: null
+ popExit: null
+ pushEnter: null
+ pushExit: null
+ replaceEnter: null
+ replaceExit: null
+ }
+
+ MNavbar {
+ id: navbar
+ visible: stackView.pageHasNavbar
+ Layout.preferredHeight: 66
+ Layout.fillWidth: true
+ }
+ }
+
+ Component {
+ id: startPage
+ MStartPage {}
+ }
+ Component {
+ id: loginPage
+ MLoginPage {}
+ }
+ Component {
+ id: registerPage
+ MRegisterPage {}
+ }
+ Component {
+ id: homePage
+ MLibraryPage {}
+ }
+ Component {
+ id: readingPage
+ MReadingPage {}
+ }
+ Component {
+ id: explorePage
+ MExplorePage {}
+ }
+ Component {
+ id: profilePage
+ MProfilePage {}
+ }
+ Component {
+ id: myAccountPage
+ MMyAccountPage {}
+ }
+
+
+ /*
+ loadPage() manages the page switching through out the application
+ */
+ function loadPage(page, navbarItem = undefined) {
+ if (page === stackView.currentItem)
+ return
+
+ stackView.pageHasNavbar = navbarItem !== undefined
+
+ if (navbarItem !== undefined) {
+ navbar.currentItem = navbarItem
+ }
+
+ stackView.push(page)
+ }
+}
diff --git a/src/presentation/mobile/modules/CppElements/page_view.cpp b/src/presentation/mobile/modules/CppElements/page_view.cpp
new file mode 100644
index 000000000..d1e323f68
--- /dev/null
+++ b/src/presentation/mobile/modules/CppElements/page_view.cpp
@@ -0,0 +1,654 @@
+#include "page_view.hpp"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "fz_utils.hpp"
+#include "highlight.hpp"
+#include "page_controller.hpp"
+
+using adapters::IBookController;
+using adapters::controllers::PageController;
+using domain::entities::Highlight;
+using namespace application::core;
+
+namespace cpp_elements
+{
+
+cpp_elements::PageView::PageView()
+{
+ setFlag(QQuickItem::ItemHasContents, true);
+ setAcceptedMouseButtons(Qt::AllButtons);
+ setAcceptHoverEvents(true);
+
+ m_tripleClickTimer.setInterval(400);
+ m_tripleClickTimer.setSingleShot(true);
+}
+
+void PageView::setBookController(IBookController* newBookController)
+{
+ m_bookController = newBookController;
+
+ m_pageController = std::make_unique(
+ m_bookController->getFzDocument(), m_pageNumber,
+ window()->devicePixelRatio());
+ m_pageController->setZoom(m_bookController->getZoom());
+
+ // Setup connections to the BookController
+ connect(m_bookController, &IBookController::zoomChanged, this,
+ &PageView::updateZoom);
+
+ connect(m_bookController, &IBookController::selectText, this,
+ [this](int pageNumber, QPointF left, QPointF right)
+ {
+ if(pageNumber != m_pageNumber)
+ return;
+
+ // The offsets are only valid once the page was rendered, make
+ // sure that it is always rendered first.
+ m_pageController->renderPage(m_pageNumber);
+
+ auto xOffset = m_pageController->getXOffset();
+ auto yOffset = m_pageController->getYOffset();
+ left = QPoint(left.x() - xOffset, left.y() - yOffset);
+ right = QPoint(right.x() - xOffset, right.y() - yOffset);
+
+ // The points received from this signal are relative to a zoom
+ // of 1, but all of the methods in this class handle points as
+ // if they have the currentZoom applied, so we need to scale it.
+ // We need to divide by the dpr since the lower level methods
+ // expect the caller not to know anything about the dpr, but the
+ // current zoom we get from the page controller is zoom * dpr.
+ // Thus we need to reset it to the zoom without the dpr.
+ auto zoom = m_pageController->getZoom();
+ auto dpr = window()->devicePixelRatio();
+ left = utils::scalePointToCurrentZoom(left, 1, zoom / dpr);
+ right = utils::scalePointToCurrentZoom(right, 1, zoom / dpr);
+
+ m_selectionStart = left;
+ m_selectionEnd = right;
+
+ createSelection();
+ });
+}
+
+int PageView::getImplicitWidth() const
+{
+ return m_pageController->getWidth();
+}
+
+int PageView::getImplicitHeight() const
+{
+ return m_pageController->getHeight();
+}
+
+int PageView::getPageNumber() const
+{
+ return m_pageNumber;
+}
+
+void PageView::setPageNumber(int newCurrentPage)
+{
+ m_pageNumber = newCurrentPage;
+}
+
+void PageView::updateZoom(float newZoom)
+{
+ auto oldZoom = m_pageController->getZoom();
+ m_pageController->setZoom(newZoom);
+
+ // Update selection positions to match new zoom
+ if(!m_selectionStart.isNull() && !m_selectionEnd.isNull())
+ {
+ m_selectionStart =
+ utils::scalePointToCurrentZoom(m_selectionStart, oldZoom, newZoom);
+ m_selectionEnd =
+ utils::scalePointToCurrentZoom(m_selectionEnd, oldZoom, newZoom);
+
+ m_pageController->generateSelectionRects(m_selectionStart,
+ m_selectionEnd);
+ }
+
+ emit implicitWidthChanged();
+ emit implicitHeightChanged();
+}
+
+void PageView::geometryChange(const QRectF& newGeometry,
+ const QRectF& oldGeometry)
+{
+ if(newGeometry.size().isEmpty())
+ return;
+
+ if(newGeometry.width() != oldGeometry.width() ||
+ newGeometry.height() != newGeometry.height())
+ {
+ update();
+ }
+
+ QQuickItem::geometryChange(newGeometry, oldGeometry);
+}
+
+QSGNode* PageView::updatePaintNode(QSGNode* node, UpdatePaintNodeData* nodeData)
+{
+ Q_UNUSED(nodeData);
+ QSGSimpleTextureNode* n = static_cast(node);
+ if(!n)
+ {
+ n = new QSGSimpleTextureNode();
+ n->setOwnsTexture(true);
+ }
+
+ auto image = m_pageController->renderPage(m_pageNumber);
+ QPainter painter(&image);
+
+ paintSelectionOnPage(painter);
+ paintHighlightsOnPage(painter);
+
+ n->setTexture(window()->createTextureFromImage(image));
+ n->setRect(boundingRect());
+ return n;
+}
+
+void PageView::mouseDoubleClickEvent(QMouseEvent* event)
+{
+ if(event->button() == Qt::RightButton)
+ return;
+
+ if(m_startedMousePressOnHighlight)
+ return;
+
+ int mouseX = event->position().x();
+ int mouseY = event->position().y();
+ QPoint mousePoint(mouseX, mouseY);
+
+ m_selectionStart = mousePoint;
+ m_selectionEnd = mousePoint;
+ selectSingleWord();
+
+ m_tripleClickTimer.start();
+ m_doubleClickHold = true;
+}
+
+void PageView::mousePressEvent(QMouseEvent* event)
+{
+ if(event->button() == Qt::RightButton)
+ return;
+
+ int mouseX = event->position().x();
+ int mouseY = event->position().y();
+ QPoint point(mouseX, mouseY);
+
+ forceActiveFocus();
+
+ if(m_pageController->pointIsAboveLink(point))
+ m_startedMousePressOnLink = true;
+
+ auto highlight = m_bookController->getHighlightAtPoint(point, m_pageNumber);
+ if(highlight != nullptr)
+ {
+ handleClickingOnHighlight(highlight);
+ return;
+ }
+ m_startedMousePressOnHighlight = false;
+
+ // Select line when left mouse button is pressed 3 times
+ if(m_tripleClickTimer.isActive())
+ {
+ selectLine();
+ return;
+ }
+
+ m_selectionStart = point;
+}
+
+void PageView::handleClickingOnHighlight(const Highlight* highlight)
+{
+ // Scale the highlight rects (initially zoom of 1) to the current zoom and
+ // get the center x and bottom y position of the highlight.
+ auto rects = highlight->getRects();
+ QList qRects;
+ qRects.reserve(rects.size());
+ for(auto& rect : rects)
+ {
+ auto qRectF = rect.getQRect();
+ utils::scaleQRectFToZoom(
+ qRectF, m_pageController->getZoom() / window()->devicePixelRatio());
+ qRects.append(qRectF);
+ }
+ auto positions = getCenterXAndBottomYFromRects(qRects);
+
+ auto uuidAsString = highlight->getUuid().toString(QUuid::WithoutBraces);
+ m_bookController->highlightSelected(positions.first, positions.second,
+ uuidAsString);
+ m_startedMousePressOnHighlight = true;
+}
+
+void PageView::mouseReleaseEvent(QMouseEvent* event)
+{
+ int mouseX = event->position().x();
+ int mouseY = event->position().y();
+ QPoint mousePoint(mouseX, mouseY);
+
+ if(m_startedMousePressOnLink &&
+ m_pageController->pointIsAboveLink(mousePoint))
+ {
+ auto uri = m_pageController->getLinkUriAtPoint(mousePoint);
+ m_bookController->followLink(uri);
+ }
+ m_startedMousePressOnLink = false;
+
+ // This gets triggered when the user simply clicks on the page, without
+ // dragging the mouse, so on a normal click. In this case we want to
+ // reset the selection.
+ if(m_selectionStart == QPointF(mouseX, mouseY) &&
+ !m_tripleClickTimer.isActive())
+ {
+ removeSelection();
+
+ // Restart it since some actions are checking if it is active to e.g.
+ // prevent removing the line select on mouse release
+ m_tripleClickTimer.start();
+ }
+ else if(!m_startedMousePressOnHighlight)
+ {
+ auto rects = m_pageController->getBufferedSelectionRects();
+ QList restoredRects;
+ restoredRects.reserve(rects.size());
+ for(auto rect : rects)
+ {
+ utils::restoreQRect(rect, m_pageController->getZoom());
+ utils::scaleQRectFToZoom(rect, m_pageController->getZoom() /
+ window()->devicePixelRatio());
+ restoredRects.push_back(rect);
+ }
+
+ auto positions = getCenterXAndBottomYFromRects(restoredRects);
+
+ emit m_bookController->textSelectionFinished(positions.first,
+ positions.second);
+ }
+
+ m_doubleClickHold = false;
+}
+
+QPair PageView::getCenterXAndBottomYFromRects(
+ const QList& rects)
+{
+ float mostLeftX = std::numeric_limits::max();
+ float mostRightX = 0;
+ float bottomY = 0;
+ for(auto& rect : rects)
+ {
+ if(rect.x() < mostLeftX)
+ mostLeftX = rect.x();
+
+ if(rect.x() + rect.width() > mostRightX)
+ mostRightX = rect.x() + rect.width();
+
+ if(rect.bottom() > bottomY)
+ bottomY = rect.bottom();
+ }
+
+ auto centerX = (mostLeftX + mostRightX) / 2;
+ return { centerX, bottomY };
+}
+
+void PageView::mouseMoveEvent(QMouseEvent* event)
+{
+ if(event->buttons() == Qt::RightButton)
+ return;
+
+ if(m_startedMousePressOnHighlight)
+ return;
+
+ int mouseX = event->position().x();
+ int mouseY = event->position().y();
+
+ // 'hoverMoveEvent' is not triggered when the left mouse button is pressed,
+ // thus the cursor will not change correctly. Make sure to handle it here.
+ setCorrectCursor(mouseX, mouseY);
+
+ m_selectionEnd = QPointF(mouseX, mouseY);
+ if(m_doubleClickHold)
+ selectMultipleWords();
+ else
+ createSelection();
+}
+
+void PageView::hoverMoveEvent(QHoverEvent* event)
+{
+ int mouseX = event->position().x();
+ int mouseY = event->position().y();
+ setCorrectCursor(mouseX, mouseY);
+
+ emit mouseHoverMoved();
+}
+
+void PageView::keyPressEvent(QKeyEvent* event)
+{
+ if(event->key() == Qt::Key_C && event->modifiers() == Qt::ControlModifier)
+ {
+ copySelectedText();
+ }
+}
+
+void PageView::hoverLeaveEvent(QHoverEvent* event)
+{
+ resetCursorToDefault();
+}
+
+void PageView::paintSelectionOnPage(QPainter& painter)
+{
+ auto& selectionRects = m_pageController->getBufferedSelectionRects();
+ for(auto rect : selectionRects)
+ {
+ QColor selectionColor(134, 171, 175, 125);
+ painter.setCompositionMode(QPainter::CompositionMode_Multiply);
+ painter.fillRect(rect, selectionColor);
+ }
+}
+
+void PageView::paintHighlightsOnPage(QPainter& painter)
+{
+ for(auto& highlight : m_bookController->getHighlights())
+ {
+ if(highlight.getPageNumber() != m_pageNumber)
+ continue;
+
+ for(auto rect : highlight.getRects())
+ {
+ // We store the highlights zoom independent, so we need to scale
+ // them to the current zoom here.
+ auto qRect = rect.getQRect();
+ utils::scaleQRectFToZoom(qRect, m_pageController->getZoom());
+ rect.setQRect(qRect);
+
+ painter.setCompositionMode(QPainter::CompositionMode_Multiply);
+ painter.fillRect(rect.getQRect(), highlight.getColor());
+ }
+ }
+}
+
+void PageView::removeConflictingHighlights(Highlight& highlight)
+{
+ bool existingHighlightRemoved = false;
+
+ auto& highlights = m_bookController->getHighlights();
+ for(int i = 0; i < highlights.size(); ++i)
+ {
+ auto& existingHighlight = highlights[i];
+ if(existingHighlight.getPageNumber() != highlight.getPageNumber())
+ continue;
+
+ for(int u = 0; u < highlight.getRects().size(); ++u)
+ {
+ auto rect = highlight.getRects()[u].getQRect();
+
+ for(int k = 0; k < existingHighlight.getRects().size(); ++k)
+ {
+ auto existingRect = existingHighlight.getRects()[k].getQRect();
+
+ // New rect intersects with old rect
+ if(rect.intersects(existingRect))
+ {
+ // Make sure that the rects are on the same line. Depending
+ // on the line height, lines above eachother will overlap,
+ // but its only an intersect when they are on the same line.
+ bool onSameLine = rectsAreOnSameLine(existingRect, rect);
+ if(onSameLine)
+ {
+ auto uuid = highlights[i].getUuid();
+ m_bookController->removeHighlight(uuid);
+ --i;
+ existingHighlightRemoved = true;
+ break;
+ }
+ }
+ }
+
+ if(existingHighlightRemoved)
+ {
+ existingHighlightRemoved = false;
+ break;
+ }
+ }
+ }
+}
+
+bool PageView::mouseAboveSelection(const QPointF mouse)
+{
+ auto& selectionRects = m_pageController->getBufferedSelectionRects();
+ for(auto& rect : selectionRects)
+ {
+ if(rect.contains(mouse))
+ return true;
+ }
+
+ return false;
+}
+
+QString PageView::createHighlightFromCurrentSelection(const QString& hex,
+ int alpha)
+{
+ auto bufferedSelectionRects = m_pageController->getBufferedSelectionRects();
+
+ // Make sure to restore the rects to their original size, since we want to
+ // store them, so they need to be zoom independent.
+ for(auto& rect : bufferedSelectionRects)
+ {
+ utils::restoreQRect(rect, m_pageController->getZoom());
+ }
+
+ removeSelection();
+
+ auto pageNumber = m_pageNumber;
+ auto color = QColor(hex);
+ color.setAlpha(alpha);
+ auto rects = bufferedSelectionRects;
+ Highlight highlight(pageNumber, color);
+ highlight.setRects(rects);
+
+ removeConflictingHighlights(highlight);
+ m_bookController->addHighlight(highlight);
+
+ update();
+ return highlight.getUuid().toString(QUuid::WithoutBraces);
+}
+
+void PageView::removeHighlight(const QString& uuid)
+{
+ m_bookController->removeHighlight(QUuid(uuid));
+
+ update();
+}
+
+void PageView::changeHighlightColor(const QString& uuid, const QString& color,
+ int alpha)
+{
+ QColor newColor(color);
+ newColor.setAlpha(alpha);
+
+ m_bookController->changeHighlightColor(QUuid(uuid), newColor);
+
+ update();
+}
+
+void PageView::copyHighlightedText(const QString& uuid)
+{
+ auto text = getHighlightedText(uuid);
+
+ auto clipboard = QApplication::clipboard();
+ clipboard->setText(text);
+}
+
+QString PageView::getSelectedText()
+{
+ return m_pageController->getTextFromSelection(m_selectionStart,
+ m_selectionEnd);
+}
+
+QString PageView::getHighlightedText(const QString& uuid)
+{
+ const Highlight* highlight = nullptr;
+ for(auto& h : m_bookController->getHighlights())
+ {
+ if(h.getUuid() == QUuid(uuid))
+ {
+ highlight = &h;
+ break;
+ }
+ }
+
+ QPointF start(highlight->getRects().first().getQRect().left(),
+ highlight->getRects().first().getQRect().center().y());
+
+ QPointF end(highlight->getRects().last().getQRect().right(),
+ highlight->getRects().last().getQRect().center().y());
+
+ start =
+ utils::scalePointToCurrentZoom(start, 1, m_bookController->getZoom());
+ end = utils::scalePointToCurrentZoom(end, 1, m_bookController->getZoom());
+
+ return m_pageController->getTextFromSelection(start, end);
+}
+
+void PageView::setPointingCursor()
+{
+ if(QApplication::overrideCursor() == nullptr ||
+ *QApplication::overrideCursor() != Qt::PointingHandCursor)
+ {
+ resetCursorToDefault();
+ QApplication::setOverrideCursor(Qt::PointingHandCursor);
+ }
+}
+
+void PageView::createSelection()
+{
+ m_pageController->generateSelectionRects(m_selectionStart, m_selectionEnd);
+ update();
+}
+
+void PageView::removeSelection()
+{
+ m_pageController->clearBufferedSelectionRects();
+ update();
+
+ m_selectionStart = QPointF(0, 0);
+ m_selectionEnd = QPointF(0, 0);
+}
+
+void PageView::selectSingleWord()
+{
+ auto points = m_pageController->getPositionsForWordSelection(
+ m_selectionStart, m_selectionEnd);
+
+ m_selectionStart = points.first;
+ m_selectionEnd = points.second;
+
+ createSelection();
+}
+
+void PageView::selectMultipleWords()
+{
+ auto positions = m_pageController->getPositionsForWordSelection(
+ m_selectionStart, m_selectionEnd);
+
+ m_selectionStart = positions.first;
+ m_selectionEnd = positions.second;
+
+ createSelection();
+}
+
+void PageView::selectLine()
+{
+ auto positions =
+ m_pageController->getPositionsForLineSelection(m_selectionStart);
+
+ m_selectionStart = positions.first;
+ m_selectionEnd = positions.second;
+
+ createSelection();
+}
+
+void PageView::copySelectedText()
+{
+ QString text = getSelectedText();
+ if(!m_includeNewLinesInCopiedText)
+ {
+ text.replace("\n", "");
+ text.replace("\r", "");
+ }
+
+ auto clipboard = QApplication::clipboard();
+ clipboard->setText(text);
+}
+
+void PageView::resetCursorToDefault()
+{
+ QApplication::restoreOverrideCursor();
+}
+
+void PageView::setCorrectCursor(int x, int y)
+{
+ if(m_pageController->pointIsAboveLink(QPoint(x, y)) ||
+ m_bookController->getHighlightAtPoint(QPointF(x, y), m_pageNumber))
+ {
+ setPointingCursor();
+ }
+ else if(m_pageController->pointIsAboveText(QPoint(x, y)))
+ {
+ if(QApplication::overrideCursor() == nullptr ||
+ *QApplication::overrideCursor() != Qt::IBeamCursor)
+ {
+ resetCursorToDefault();
+ QApplication::setOverrideCursor(Qt::IBeamCursor);
+ }
+ }
+ else
+ {
+ resetCursorToDefault();
+ }
+}
+
+bool PageView::rectsAreOnSameLine(const QRectF& rect1, const QRectF& rect2)
+{
+ auto shorterRect = rect1.height() <= rect2.height() ? rect1 : rect2;
+ auto intersectH = rect1.intersected(rect2).height();
+
+ float offsetTolerance = 0.75;
+ bool onSameLine = intersectH >= shorterRect.height() * offsetTolerance;
+
+ return onSameLine;
+}
+
+void PageView::setColorInverted(bool newColorInverted)
+{
+ // This method gets called on initialisation of the page item, but we don't
+ // want to redraw it then, so we skip it if it's called for the first time.
+ m_pageController->setInvertColor(newColorInverted);
+ if(!m_firstTimeColorInverted)
+ update();
+
+ m_firstTimeColorInverted = false;
+}
+
+void PageView::setIncludeNewLinesInCopiedText(
+ bool newIncludeNewLinesInCopiedText)
+{
+ m_includeNewLinesInCopiedText = newIncludeNewLinesInCopiedText;
+}
+
+float PageView::getYOffset() const
+{
+ return m_pageController->getYOffset();
+}
+
+} // namespace cpp_elements
\ No newline at end of file
diff --git a/src/presentation/mobile/modules/CppElements/page_view.hpp b/src/presentation/mobile/modules/CppElements/page_view.hpp
new file mode 100644
index 000000000..a8d874aae
--- /dev/null
+++ b/src/presentation/mobile/modules/CppElements/page_view.hpp
@@ -0,0 +1,118 @@
+#include
+#include
+#include
+#include
+#include
+#include "i_book_controller.hpp"
+#include "page_controller.hpp"
+#include "presentation_export.hpp"
+
+namespace cpp_elements
+{
+
+/**
+ * This class is responsible for rendering a single page of the book.
+ * It derives from QQuickItem, which is the base class for all visual item
+ * types in Qml. It is a visual item that can be drawn on the screen and
+ * interacted with.
+ */
+class PRESENTATION_EXPORT PageView : public QQuickItem
+{
+ Q_OBJECT
+ Q_PROPERTY(adapters::IBookController* bookController WRITE setBookController
+ CONSTANT)
+ Q_PROPERTY(
+ int implicitWidth READ getImplicitWidth NOTIFY implicitWidthChanged)
+ Q_PROPERTY(
+ int implicitHeight READ getImplicitHeight NOTIFY implicitHeightChanged)
+ Q_PROPERTY(int pageNumber READ getPageNumber WRITE setPageNumber CONSTANT)
+ Q_PROPERTY(bool colorInverted WRITE setColorInverted)
+ Q_PROPERTY(float yOffset READ getYOffset CONSTANT)
+ Q_PROPERTY(
+ bool includeNewLinesInCopiedText WRITE setIncludeNewLinesInCopiedText)
+
+public:
+ PageView();
+
+ int getImplicitWidth() const;
+ int getImplicitHeight() const;
+
+ float getYOffset() const;
+
+ bool disableHoverEvents() const;
+ void setDisableHoverEvents(bool newDisableHoverEvents);
+
+ int getPageNumber() const;
+ void setPageNumber(int newCurrentPage);
+ void setColorInverted(bool newColorInverted);
+
+ void setIncludeNewLinesInCopiedText(bool newIncludeNewLinesInCopiedText);
+
+ void setBookController(adapters::IBookController* newBookController);
+
+ Q_INVOKABLE void copySelectedText();
+ Q_INVOKABLE void copyHighlightedText(const QString& uuid);
+ Q_INVOKABLE QString getSelectedText();
+ Q_INVOKABLE QString getHighlightedText(const QString& uuid);
+
+ Q_INVOKABLE void removeSelection();
+ Q_INVOKABLE void setPointingCursor();
+ Q_INVOKABLE void resetCursorToDefault();
+ Q_INVOKABLE QString createHighlightFromCurrentSelection(const QString& hex,
+ int alpha);
+ Q_INVOKABLE void removeHighlight(const QString& uuid);
+ Q_INVOKABLE void changeHighlightColor(const QString& uuid,
+ const QString& color, int alpha);
+
+signals:
+ void mouseHoverMoved();
+
+private slots:
+ void updateZoom(float newZoom);
+
+protected:
+ void geometryChange(const QRectF& newGeometry,
+ const QRectF& oldGeometry) override;
+ QSGNode* updatePaintNode(QSGNode* node, UpdatePaintNodeData* _) override;
+
+ virtual void mouseDoubleClickEvent(QMouseEvent* event) override;
+ virtual void mousePressEvent(QMouseEvent* event) override;
+ virtual void mouseReleaseEvent(QMouseEvent* event) override;
+ virtual void mouseMoveEvent(QMouseEvent* event) override;
+ virtual void hoverMoveEvent(QHoverEvent* event) override;
+ virtual void keyPressEvent(QKeyEvent* event) override;
+ virtual void hoverLeaveEvent(QHoverEvent* event) override;
+
+private:
+ void selectSingleWord();
+ void selectMultipleWords();
+ void selectLine();
+ void createSelection();
+ void paintSelectionOnPage(QPainter& painter);
+
+ void paintHighlightsOnPage(QPainter& painter);
+ void handleClickingOnHighlight(
+ const domain::entities::Highlight* highlight);
+ void removeConflictingHighlights(domain::entities::Highlight& highlight);
+ bool mouseAboveSelection(const QPointF mouse);
+
+ void setCorrectCursor(int x, int y);
+
+ bool rectsAreOnSameLine(const QRectF& rect1, const QRectF& rect2);
+ QPair getCenterXAndBottomYFromRects(
+ const QList& rects);
+
+ std::unique_ptr m_pageController;
+ adapters::IBookController* m_bookController = nullptr;
+ int m_pageNumber = 0;
+ bool m_firstTimeColorInverted = true;
+ bool m_startedMousePressOnLink = false;
+ bool m_startedMousePressOnHighlight = false;
+ QPointF m_selectionStart;
+ QPointF m_selectionEnd;
+ QTimer m_tripleClickTimer;
+ bool m_doubleClickHold = false;
+ bool m_includeNewLinesInCopiedText = false;
+};
+
+} // namespace cpp_elements
\ No newline at end of file
diff --git a/src/presentation/mobile/modules/CppElements/presentation_export.hpp b/src/presentation/mobile/modules/CppElements/presentation_export.hpp
new file mode 100644
index 000000000..0b680a79c
--- /dev/null
+++ b/src/presentation/mobile/modules/CppElements/presentation_export.hpp
@@ -0,0 +1,8 @@
+#pragma once
+#include
+
+#if defined(PRESENTATION_LIBRARY)
+ #define PRESENTATION_EXPORT Q_DECL_EXPORT
+#else
+ #define PRESENTATION_EXPORT Q_DECL_IMPORT
+#endif
\ No newline at end of file
diff --git a/src/presentation/mobile/modules/CustomComponents/MCheckBox.qml b/src/presentation/mobile/modules/CustomComponents/MCheckBox.qml
new file mode 100644
index 000000000..27955e0c5
--- /dev/null
+++ b/src/presentation/mobile/modules/CustomComponents/MCheckBox.qml
@@ -0,0 +1,64 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Librum.style
+import Librum.icons
+
+Item {
+ id: root
+ property color borderColor: Style.colorCheckboxBorder
+ property color checkedBorderColor: Style.colorContainerBorder
+ property int borderRadius: 4
+ property int borderWidth: 1
+ property int checkedBorderWidth: 0
+ property color uncheckedBackgroundColor: "transparent"
+ property color checkedBackgroundColor: Style.colorBasePurple
+ property string image: Icons.checkWhite
+ property int imageSize: container.width - 10
+ property bool checked: false
+ property bool enabled: true
+ signal clicked
+
+ implicitWidth: 22
+ implicitHeight: 22
+
+ Rectangle {
+ id: container
+ anchors.fill: parent
+ radius: root.borderRadius
+ border.width: root.checked ? root.checkedBorderWidth : root.borderWidth
+ border.color: root.activeFocus ? root.checkedBorderColor : root.borderColor
+ color: root.checked ? root.checkedBackgroundColor : root.uncheckedBackgroundColor
+ antialiasing: true
+
+ Image {
+ id: image
+ anchors.centerIn: parent
+ visible: root.checked ? true : false
+ sourceSize.width: root.imageSize
+ source: root.image
+ fillMode: Image.PreserveAspectFit
+ }
+
+ MouseArea {
+ id: mouseArea
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+
+ onClicked: root.toggle()
+ }
+ }
+
+ function toggle() {
+ if (!root.enabled)
+ return
+
+ root.clicked()
+ root.checked = !root.checked
+ }
+
+ function giveFocus() {
+ root.forceActiveFocus()
+ }
+}
diff --git a/src/presentation/mobile/modules/CustomComponents/MFlickWrapper.qml b/src/presentation/mobile/modules/CustomComponents/MFlickWrapper.qml
new file mode 100644
index 000000000..e0e933a5c
--- /dev/null
+++ b/src/presentation/mobile/modules/CustomComponents/MFlickWrapper.qml
@@ -0,0 +1,13 @@
+import QtQuick
+
+
+/**
+ A component supposed to be wrapped around pages or popups to make
+ them scrollable by wrapping a pre-configured Flickable around them.
+*/
+Flickable {
+ id: root
+ flickableDirection: Flickable.VerticalFlick
+ maximumFlickVelocity: 300
+ flickDeceleration: 2500
+}
diff --git a/src/presentation/mobile/modules/CustomComponents/MLabeledInputBox.qml b/src/presentation/mobile/modules/CustomComponents/MLabeledInputBox.qml
new file mode 100644
index 000000000..d99e37417
--- /dev/null
+++ b/src/presentation/mobile/modules/CustomComponents/MLabeledInputBox.qml
@@ -0,0 +1,248 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Librum.style
+import Librum.fonts
+import Librum.icons
+
+Item {
+ id: root
+ property alias text: input.text
+ property bool selected: false
+ property color inputFontColor: Style.colorText
+ property bool readOnly: false
+ property bool textHidden: false
+ property int inputFontSize: Fonts.size13
+ property string placeHolderText: "Input"
+ property color placeHolderColor: Style.colorPlaceholderText
+ property int fontWeight: Font.Normal
+ property bool isPassword: false
+
+ implicitWidth: 200
+ implicitHeight: 56
+
+ // If there is some preconfigured text, we want to move the placeholder
+ // to the header position
+ Component.onCompleted: {
+ if (text.length == 0)
+ return
+
+ moveablePlaceholder.y = internal.yHeaderDest
+ moveablePlaceholder.font.pointSize = internal.headerPlaceholderSize
+ textOverlay.width = internal.widthHeaderDest
+ textOverlay.x = textOverlay.expandedPosition
+ internal.inHeaderMode = true
+ }
+
+ Rectangle {
+ id: background
+ anchors.fill: parent
+ color: "transparent"
+ border.width: input.activeFocus ? 2 : 1
+ border.color: input.activeFocus ? Style.colorBasePurple : "#9999A0"
+ radius: 6
+
+ Rectangle {
+ id: textOverlay
+ property int expandedWidth: movedPlaceholderReference.implicitWidth
+ + textOverlay.sidePadding
+ property int defaultPosition: 12 + expandedWidth / 2
+ property int expandedPosition: 12
+ property int sidePadding: 8
+
+ width: 0
+ height: 4
+ y: -(height / 2)
+ x: defaultPosition
+ color: Style.colorPageBackground
+ }
+ }
+
+ RowLayout {
+ anchors.fill: parent
+ spacing: 0
+
+ TextField {
+ id: input
+ Layout.fillWidth: true
+ selectByMouse: true
+ readOnly: root.readOnly
+ color: root.inputFontColor
+ font.pointSize: root.inputFontSize
+ leftPadding: 16
+ rightPadding: 16
+ echoMode: root.textHidden ? TextInput.Password : TextInput.Normal
+ selectionColor: Style.colorTextSelection
+ selectedTextColor: root.inputFontColor
+ font.weight: root.fontWeight
+ background: Rectangle {
+ anchors.fill: parent
+ radius: 6
+ color: "transparent"
+ }
+
+ onActiveFocusChanged: {
+ if (activeFocus && !internal.inHeaderMode
+ && input.text.length === 0) {
+ moveToHeaderAnim.start()
+ } else if (!activeFocus && internal.inHeaderMode
+ && input.text.length === 0) {
+ moveBackAnim.start()
+ }
+ }
+
+ Label {
+ id: moveablePlaceholder
+ property int centeredPosition
+
+ x: input.leftPadding
+ font.pointSize: root.inputFontSize
+ y: centeredPosition
+ color: root.placeHolderColor
+ text: root.placeHolderText
+
+ // We want to compute this only once at the start
+ Component.onCompleted: centeredPosition = (parent.height - implicitHeight) / 2
+ }
+
+
+ /**
+ Since the label in the header has a smaller size than the placeholder label
+ we need to use that smaller size to calculate with. This is just a non-rendered
+ object to calculate with.
+ */
+ Label {
+ id: movedPlaceholderReference
+ property int centeredPosition: (parent.height - implicitHeight) / 2
+
+ visible: false
+ x: input.leftPadding
+ y: centeredPosition
+ font.pointSize: internal.headerPlaceholderSize
+ text: root.placeHolderText
+ }
+ }
+
+ Item {
+ Layout.preferredWidth: 42
+ Layout.rightMargin: 8
+ Layout.fillHeight: true
+ visible: root.isPassword
+
+ IconImage {
+ id: passwordVisibilityTogglerIcon
+ anchors.centerIn: parent
+ Layout.alignment: Qt.AlignVCenter
+ source: root.textHidden ? Icons.eyeOn : Icons.eyeOff
+ opacity: imageArea.pressed ? 0.75 : 1
+ sourceSize.width: 22
+ fillMode: Image.PreserveAspectFit
+ color: "#9999A0"
+ }
+
+ TapHandler {
+ id: imageArea
+ onTapped: root.textHidden = !root.textHidden
+ }
+ }
+ }
+
+ TapHandler {
+ onTapped: input.forceActiveFocus()
+ }
+
+ ParallelAnimation {
+ id: moveToHeaderAnim
+ property int zeroRelativeToLabel: mapToItem(
+ movedPlaceholderReference,
+ Qt.point(
+ movedPlaceholderReference.x,
+ 0)).y
+ property int halfLabelHeight: movedPlaceholderReference.implicitHeight / 2
+
+ NumberAnimation {
+ target: moveablePlaceholder
+ property: "y"
+ to: internal.yHeaderDest
+ duration: 340
+ easing.type: Easing.InOutQuad
+ }
+
+ NumberAnimation {
+ target: moveablePlaceholder
+ property: "font.pointSize"
+ to: internal.headerPlaceholderSize
+ duration: 340
+ easing.type: Easing.InOutQuad
+ }
+
+ NumberAnimation {
+ target: textOverlay
+ property: "width"
+ to: internal.widthHeaderDest
+ duration: 340
+ easing.type: Easing.InOutQuad
+ }
+
+ NumberAnimation {
+ target: textOverlay
+ property: "x"
+ to: textOverlay.expandedPosition
+ duration: 340
+ easing.type: Easing.InOutQuad
+ }
+
+ onFinished: internal.inHeaderMode = true
+ }
+
+ ParallelAnimation {
+ id: moveBackAnim
+
+ NumberAnimation {
+ target: moveablePlaceholder
+ property: "y"
+ to: moveablePlaceholder.centeredPosition
+ duration: 340
+ easing.type: Easing.InOutQuad
+ }
+
+ NumberAnimation {
+ target: moveablePlaceholder
+ property: "font.pointSize"
+ to: root.inputFontSize
+ duration: 340
+ easing.type: Easing.InOutQuad
+ }
+
+ NumberAnimation {
+ target: textOverlay
+ property: "width"
+ to: 0
+ duration: 340
+ easing.type: Easing.InOutQuad
+ }
+
+ NumberAnimation {
+ target: textOverlay
+ property: "x"
+ to: textOverlay.defaultPosition
+ duration: 340
+ easing.type: Easing.InOutQuad
+ }
+
+ onFinished: internal.inHeaderMode = false
+ }
+
+ QtObject {
+ id: internal
+ property bool inHeaderMode: false
+ property int yHeaderDest: moveToHeaderAnim.zeroRelativeToLabel
+ - moveToHeaderAnim.halfLabelHeight
+ - movedPlaceholderReference.centeredPosition - 1
+ property int widthHeaderDest: movedPlaceholderReference.implicitWidth
+ + textOverlay.sidePadding
+
+ property int headerPlaceholderSize: Fonts.size12dot5
+ property bool textHiden: root.isPassword
+ }
+}
diff --git a/src/presentation/mobile/modules/CustomComponents/MLogo.qml b/src/presentation/mobile/modules/CustomComponents/MLogo.qml
new file mode 100644
index 000000000..91a068bbc
--- /dev/null
+++ b/src/presentation/mobile/modules/CustomComponents/MLogo.qml
@@ -0,0 +1,27 @@
+import QtQuick
+import QtQuick.Layouts
+import Librum.style
+
+Item {
+ id: root
+ implicitWidth: 72
+ implicitHeight: 72
+
+ Rectangle {
+ id: outerRect
+ anchors.fill: parent
+ color: Style.colorBasePurple
+ radius: 4
+ antialiasing: true
+
+ Rectangle {
+ id: innerRect
+ width: parent.width / 2
+ height: parent.height / 2
+ anchors.centerIn: parent
+ color: Style.colorContainerBackground
+ radius: width / 7
+ antialiasing: true
+ }
+ }
+}
diff --git a/src/presentation/mobile/modules/CustomComponents/buttons/MBackButton.qml b/src/presentation/mobile/modules/CustomComponents/buttons/MBackButton.qml
new file mode 100644
index 000000000..3c679e289
--- /dev/null
+++ b/src/presentation/mobile/modules/CustomComponents/buttons/MBackButton.qml
@@ -0,0 +1,35 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Librum.style
+import Librum.icons
+
+Item {
+ id: root
+ signal clicked
+
+ implicitWidth: 40
+ implicitHeight: 40
+ opacity: tapHandler.pressed ? 0.07 : 1
+
+ Rectangle {
+ anchors.fill: parent
+ color: Style.colorTransparentHighlight
+ opacity: tapHandler.pressed ? 0.5 : 0
+ radius: 6
+ }
+
+ IconImage {
+ id: backArrowIcon
+ anchors.centerIn: parent
+ source: Icons.mobileArrowLeft
+ sourceSize.width: 24
+ color: "#121212"
+ }
+
+ TapHandler {
+ id: tapHandler
+
+ onTapped: root.clicked()
+ }
+}
diff --git a/src/presentation/mobile/modules/CustomComponents/buttons/MButton.qml b/src/presentation/mobile/modules/CustomComponents/buttons/MButton.qml
new file mode 100644
index 000000000..47aec71bc
--- /dev/null
+++ b/src/presentation/mobile/modules/CustomComponents/buttons/MButton.qml
@@ -0,0 +1,79 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Librum.style
+import Librum.fonts
+
+Item {
+ id: root
+ property string text
+ property color textColor: Style.colorUnfocusedButtonText
+ property int fontWeight: Font.Bold
+ property double fontSize: Fonts.size11
+ property color backgroundColor: Style.colorControlBackground
+ property double opacityOnPressed: 0.9
+ property int borderWidth: 1
+ property color borderColor: Style.colorButtonBorder
+ property color borderColorOnPressed: borderColor
+ property int radius: 4
+ property string imagePath
+ property int imageSpacing: 8
+ property int imageSize: 15
+ property int imageLeftMargin: 0
+ property int imageRotation: 0
+ property int horizontalMargins: 1
+
+ implicitHeight: 30
+ implicitWidth: layout.implicitWidth + 2 * horizontalMargins
+
+ signal clicked
+
+ Rectangle {
+ id: container
+ anchors.fill: parent
+ color: root.backgroundColor
+ border.width: root.borderWidth
+ border.color: mouseArea.pressed ? root.borderColorOnPressed : root.borderColor
+ radius: root.radius
+ opacity: mouseArea.pressed ? root.opacityOnPressed : 1
+ antialiasing: true
+
+ RowLayout {
+ id: layout
+ anchors.centerIn: parent
+ spacing: root.imageSpacing
+
+ Image {
+ id: image
+ Layout.leftMargin: root.imageLeftMargin
+ visible: root.imagePath.length > 0 && !root.imageToRight
+ source: root.imagePath
+ sourceSize.width: root.imageSize
+ fillMode: Image.PreserveAspectFit
+ rotation: root.imageRotation
+ }
+
+ Label {
+ id: buttonText
+ visible: text.length > 0
+ Layout.preferredWidth: container.width
+ < implicitWidth ? container.width : implicitWidth
+ text: root.text
+ font.weight: root.fontWeight
+ font.pointSize: root.fontSize
+ color: root.textColor
+ elide: Text.ElideRight
+ }
+ }
+
+ TapHandler {
+ id: mouseArea
+
+ onTapped: root.clicked()
+ }
+ }
+
+ function giveFocus() {
+ root.forceActiveFocus()
+ }
+}
diff --git a/src/presentation/mobile/modules/CustomComponents/qmldir b/src/presentation/mobile/modules/CustomComponents/qmldir
new file mode 100644
index 000000000..8c585947f
--- /dev/null
+++ b/src/presentation/mobile/modules/CustomComponents/qmldir
@@ -0,0 +1,7 @@
+module CustomComponents
+MButton 1.0 buttons/MButton.qml
+MBackButton 1.0 buttons/MBackButton.qml
+MLabeledInputBox 1.0 MLabeledInputBox.qml
+MLogo 1.0 MLogo.qml
+MCheckBox 1.0 MCheckBox.qml
+MFlickWrapper 1.0 MFlickWrapper.qml
\ No newline at end of file
diff --git a/src/presentation/mobile/myAccountPage/MMyAccountPage.qml b/src/presentation/mobile/myAccountPage/MMyAccountPage.qml
new file mode 100644
index 000000000..6cd0991cf
--- /dev/null
+++ b/src/presentation/mobile/myAccountPage/MMyAccountPage.qml
@@ -0,0 +1,159 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Qt.labs.platform
+import Librum.style
+import Librum.fonts
+import Librum.icons
+import Librum.controllers
+import CustomComponents
+import "../profilePage"
+
+Page {
+ id: root
+ padding: 0
+ background: Rectangle {
+ color: Style.colorPageBackground
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+ spacing: 0
+
+ Rectangle {
+ id: topPart
+ Layout.fillWidth: true
+ Layout.preferredHeight: 100
+ color: Style.colorControlBackground
+
+ RowLayout {
+ width: parent.width
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 2
+
+ MBackButton {
+ id: backButton
+ Layout.leftMargin: 12
+ Layout.bottomMargin: -3
+
+ onClicked: loadPage(profilePage, navbar.profileItem)
+ }
+
+ Label {
+ id: pageTitle
+ Layout.alignment: Qt.AlignHCenter
+ text: qsTr("My Account")
+ color: Style.colorTitle
+ font.weight: Font.Bold
+ font.pointSize: Fonts.size20
+ }
+
+ // An element of the size of the back button to be able to correctly align the title
+ Item {
+ id: counterElem
+ Layout.preferredWidth: backButton.width
+ Layout.preferredHeight: backButton.height
+ Layout.rightMargin: 12
+ }
+ }
+ }
+
+ Rectangle {
+ id: middlePart
+ Layout.fillWidth: true
+ Layout.preferredHeight: 72
+ color: Style.colorPageBackground
+ }
+
+ Pane {
+ id: mainPart
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ verticalPadding: 0
+ horizontalPadding: 16
+ background: Rectangle {
+ color: Style.colorControlBackground
+ }
+
+ ColumnLayout {
+ width: parent.width
+ spacing: 0
+
+ MProfilePicture {
+ Layout.preferredWidth: 92
+ Layout.preferredHeight: 92
+ Layout.alignment: Qt.AlignHCenter
+ Layout.topMargin: -(Layout.preferredWidth / 2)
+ fontSize: Fonts.size34
+ }
+
+ Item {
+ id: changePictureItem
+ Layout.alignment: Qt.AlignHCenter
+ Layout.preferredHeight: changePictureText.implicitHeight * 2.2
+ Layout.preferredWidth: 140
+ Layout.topMargin: 2
+
+ Label {
+ id: changePictureText
+ anchors.centerIn: parent
+ text: "Change Picture"
+ color: Style.colorBasePurple
+ font.weight: Font.DemiBold
+ font.pointSize: Fonts.size15dot5
+ opacity: changePictureTapHandler.pressed ? 0.7 : 1
+ }
+
+ TapHandler {
+ id: changePictureTapHandler
+
+ onTapped: fileDialog.open()
+ }
+ }
+
+ MLabeledInputBox {
+ id: nameInput
+ Layout.fillWidth: true
+ Layout.preferredHeight: 54
+ Layout.topMargin: 52
+ text: UserController.name
+ placeHolderText: qsTr("Name")
+ }
+
+ MLabeledInputBox {
+ id: emailInput
+ Layout.fillWidth: true
+ Layout.preferredHeight: 54
+ Layout.topMargin: 18
+ text: UserController.name
+ placeHolderText: qsTr("Email")
+ readOnly: true
+ }
+
+ MButton {
+ id: signInButton
+ Layout.fillWidth: true
+ Layout.preferredHeight: 54
+ Layout.topMargin: 36
+ borderWidth: 0
+ radius: 40
+ backgroundColor: Style.colorBasePurple
+ fontSize: Fonts.size16
+ opacityOnPressed: 0.85
+ textColor: Style.colorFocusedButtonText
+ text: qsTr("Save Changes")
+
+ onClicked: ;
+ }
+ }
+ }
+ }
+
+ FileDialog {
+ id: fileDialog
+ fileMode: FileDialog.OpenFiles
+ folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
+
+ onAccepted: root.currentImage = file
+ }
+}
diff --git a/src/presentation/mobile/navbar/MNavbar.qml b/src/presentation/mobile/navbar/MNavbar.qml
new file mode 100644
index 000000000..07d787dce
--- /dev/null
+++ b/src/presentation/mobile/navbar/MNavbar.qml
@@ -0,0 +1,61 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Librum.style
+import Librum.icons
+import Librum.fonts
+import CustomComponents
+
+Pane {
+ id: root
+ property alias libraryItem: libraryItem
+ property alias exploreItem: exploreItem
+ property alias profileItem: profileItem
+ property MNavbarItem currentItem: null
+
+ horizontalPadding: 0
+ verticalPadding: 0
+ background: Rectangle {
+ color: "#EFEFEF"
+ }
+
+ RowLayout {
+ anchors.centerIn: parent
+ height: parent.height
+ spacing: 30
+
+ MNavbarItem {
+ id: libraryItem
+ selected: root.currentItem === libraryItem
+ icon: Icons.mobileLibrary
+ text: "Library"
+
+ onClicked: loadPage(homePage, navbar.libraryItem)
+ }
+
+ MNavbarItem {
+ icon: Icons.mobileFolder
+ text: "Folders"
+ iconHeight: 18
+ topMargin: 1
+ }
+
+ MNavbarItem {
+ id: exploreItem
+ selected: root.currentItem === exploreItem
+ icon: Icons.mobileExplore
+ text: "Explore"
+
+ onClicked: loadPage(explorePage, navbar.exploreItem)
+ }
+
+ MNavbarItem {
+ id: profileItem
+ selected: root.currentItem === profileItem
+ icon: Icons.mobileProfile
+ text: "Profile"
+
+ onClicked: loadPage(profilePage, navbar.profileItem)
+ }
+ }
+}
diff --git a/src/presentation/mobile/navbar/MNavbarItem.qml b/src/presentation/mobile/navbar/MNavbarItem.qml
new file mode 100644
index 000000000..75bd3e57f
--- /dev/null
+++ b/src/presentation/mobile/navbar/MNavbarItem.qml
@@ -0,0 +1,46 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Librum.style
+import Librum.fonts
+import CustomComponents
+
+Item {
+ id: root
+ property alias text: label.text
+ property alias icon: icon.source
+ property bool selected: false
+ property int topMargin: 0
+ property int iconHeight: 20
+ signal clicked
+
+ implicitHeight: 66
+ implicitWidth: 56
+
+ ColumnLayout {
+ anchors.centerIn: parent
+ spacing: 4
+
+ IconImage {
+ id: icon
+ Layout.alignment: Qt.AlignHCenter
+ Layout.topMargin: root.topMargin
+ opacity: tapHandler.pressed ? 0.75 : 1
+ sourceSize.height: root.iconHeight
+ fillMode: Image.PreserveAspectFit
+ color: root.selected ? Style.colorBasePurple : "#9F9F9F"
+ }
+
+ Label {
+ id: label
+ Layout.alignment: Qt.AlignHCenter
+ color: root.selected ? Style.colorBasePurple : "#9F9F9F"
+ font.pointSize: Fonts.size12
+ }
+ }
+
+ TapHandler {
+ id: tapHandler
+ onTapped: root.clicked()
+ }
+}
diff --git a/src/presentation/mobile/profilePage/MProfileCard.qml b/src/presentation/mobile/profilePage/MProfileCard.qml
new file mode 100644
index 000000000..3ccfb6879
--- /dev/null
+++ b/src/presentation/mobile/profilePage/MProfileCard.qml
@@ -0,0 +1,43 @@
+import QtQuick
+import QtQuick.Controls.Material
+import QtQuick.Layouts
+import Librum.style
+import Librum.fonts
+import Librum.controllers
+import CustomComponents
+
+Rectangle {
+ color: "transparent"
+ implicitHeight: 44
+
+ RowLayout {
+ id: layout
+
+ MProfilePicture {
+ Layout.alignment: Qt.AlignVCenter
+ }
+
+ ColumnLayout {
+ Layout.leftMargin: 10
+ Layout.topMargin: -2
+ Layout.alignment: Qt.AlignVCenter
+ spacing: -1
+
+ Label {
+ Layout.fillWidth: true
+ text: UserController.name
+ color: Style.colorText
+ font.weight: Font.DemiBold
+ font.pointSize: Fonts.size18
+ }
+
+ Label {
+ Layout.fillWidth: true
+ text: UserController.email
+ color: "#B8B3B3"
+ font.weight: Font.Thin
+ font.pointSize: Fonts.size15
+ }
+ }
+ }
+}
diff --git a/src/presentation/mobile/profilePage/MProfileItem.qml b/src/presentation/mobile/profilePage/MProfileItem.qml
new file mode 100644
index 000000000..218eedb36
--- /dev/null
+++ b/src/presentation/mobile/profilePage/MProfileItem.qml
@@ -0,0 +1,70 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Librum.style
+import Librum.fonts
+import Librum.icons
+
+Pane {
+ id: root
+ property alias text: label.text
+ property alias icon: icon.source
+ property int iconWidth: 22
+ signal clicked
+
+ implicitHeight: 44
+ horizontalPadding: 10
+ verticalPadding: 0
+ background: Rectangle {
+ id: clickHighlight
+ anchors.fill: parent
+ color: Style.colorTransparentHighlight
+ opacity: tapHandler.pressed ? 0.03 : 0
+ radius: 10
+ }
+
+ RowLayout {
+ spacing: 0
+ width: parent.width
+ anchors.verticalCenter: parent.verticalCenter
+
+ IconImage {
+ id: icon
+ Layout.alignment: Qt.AlignVCenter
+ opacity: tapHandler.pressed ? 0.75 : 1
+ sourceSize.width: root.iconWidth
+ fillMode: Image.PreserveAspectFit
+ color: Style.colorBasePurple
+ }
+
+ Label {
+ id: label
+ Layout.alignment: Qt.AlignVCenter
+ Layout.leftMargin: 14
+ font.pointSize: 16
+ font.weight: Font.Medium
+ color: Style.colorText
+ opacity: tapHandler.pressed ? 0.75 : 1
+ }
+
+ Item {
+ Layout.fillWidth: true
+ }
+
+ IconImage {
+ id: arrow
+ Layout.alignment: Qt.AlignVCenter
+ Layout.rightMargin: -8
+ source: Icons.arrowheadNextIcon
+ opacity: tapHandler.pressed ? 0.75 : 1
+ sourceSize.width: 28
+ fillMode: Image.PreserveAspectFit
+ color: "#929292"
+ }
+ }
+
+ TapHandler {
+ id: tapHandler
+ onTapped: root.clicked()
+ }
+}
diff --git a/src/presentation/mobile/profilePage/MProfilePage.qml b/src/presentation/mobile/profilePage/MProfilePage.qml
new file mode 100644
index 000000000..4f9f5c1f4
--- /dev/null
+++ b/src/presentation/mobile/profilePage/MProfilePage.qml
@@ -0,0 +1,228 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Librum.style
+import Librum.fonts
+import Librum.icons
+import Librum.controllers
+import CustomComponents
+
+Page {
+ id: root
+ horizontalPadding: 16
+ topPadding: 52
+ background: Rectangle {
+ color: Style.colorPageBackground
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+
+ Label {
+ id: pageTitle
+ Layout.alignment: Qt.AlignHCenter
+ text: qsTr("Profile")
+ color: Style.colorTitle
+ font.weight: Font.Bold
+ font.pointSize: Fonts.size19dot5
+ }
+
+ MFlickWrapper {
+ Layout.topMargin: 8
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ contentHeight: contentLayout.implicitHeight
+ clip: true
+
+ ColumnLayout {
+ id: contentLayout
+ width: parent.width
+
+ Pane {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 72
+ Layout.topMargin: 12
+ background: Rectangle {
+ color: Style.colorContainerBackground
+ radius: 10
+ }
+
+ MProfileCard {
+ width: parent.width
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+
+ Label {
+ id: accountTitle
+ Layout.topMargin: 4
+ Layout.leftMargin: 10
+ text: qsTr("Account")
+ color: Style.colorTitle
+ font.weight: Font.DemiBold
+ font.pointSize: Fonts.size16
+ }
+
+ Pane {
+ Layout.fillWidth: true
+ Layout.preferredHeight: accountLayout.implicitHeight + 2 * verticalPadding
+ Layout.topMargin: 3
+ verticalPadding: 10
+ horizontalPadding: 6
+ background: Rectangle {
+ color: Style.colorContainerBackground
+ radius: 10
+ }
+
+ ColumnLayout {
+ id: accountLayout
+ width: parent.width
+ spacing: 0
+
+ MProfileItem {
+ Layout.fillWidth: true
+ text: qsTr("My Account")
+ icon: Icons.mobileProfile
+ iconWidth: 18
+
+ onClicked: loadPage(myAccountPage)
+ }
+
+ MProfileItem {
+ Layout.fillWidth: true
+ text: qsTr("Storage")
+ icon: Icons.mobileServer
+
+ onClicked: ;
+ }
+ }
+ }
+
+ Label {
+ id: settingsTitle
+ Layout.topMargin: 4
+ Layout.leftMargin: 10
+ text: qsTr("Settings")
+ color: Style.colorTitle
+ font.weight: Font.DemiBold
+ font.pointSize: Fonts.size16
+ }
+
+ Pane {
+ Layout.fillWidth: true
+ Layout.preferredHeight: settingsLayout.implicitHeight + 2 * verticalPadding
+ Layout.topMargin: 3
+ verticalPadding: 10
+ horizontalPadding: 6
+ background: Rectangle {
+ color: Style.colorContainerBackground
+ radius: 10
+ }
+
+ ColumnLayout {
+ id: settingsLayout
+ width: parent.width
+ spacing: 0
+
+ MProfileItem {
+ Layout.fillWidth: true
+ text: qsTr("About")
+ icon: Icons.mobileDiamond
+
+ onClicked: ;
+ }
+
+ MProfileItem {
+ Layout.fillWidth: true
+ text: qsTr("Appearance")
+ icon: Icons.mobileBrush
+
+ onClicked: SettingsController.setSetting(
+ SettingKeys.Theme, "Light",
+ SettingGroups.Appearance)
+ }
+
+ MProfileItem {
+ Layout.fillWidth: true
+ text: qsTr("Behavior")
+ icon: Icons.mobileGear
+
+ onClicked: ;
+ }
+ }
+ }
+
+ Label {
+ id: settingsTitle2
+ Layout.topMargin: 4
+ Layout.leftMargin: 10
+ text: qsTr("Actions")
+ color: Style.colorTitle
+ font.weight: Font.DemiBold
+ font.pointSize: Fonts.size16
+ }
+
+ Pane {
+ Layout.fillWidth: true
+ Layout.preferredHeight: settingsLayout2.implicitHeight + 2 * verticalPadding
+ Layout.topMargin: 3
+ Layout.bottomMargin: 14
+ verticalPadding: 10
+ horizontalPadding: 6
+ background: Rectangle {
+ color: Style.colorContainerBackground
+ radius: 10
+ }
+
+ ColumnLayout {
+ id: settingsLayout2
+ width: parent.width
+ spacing: 0
+
+ MProfileItem {
+ Layout.fillWidth: true
+ text: LibraryController.isSyncing ? qsTr("Refreshing...") : qsTr(
+ "Refresh")
+ icon: Icons.mobileRefresh
+
+ onClicked: internal.reloadApplication()
+ }
+
+ MProfileItem {
+ Layout.fillWidth: true
+ text: qsTr("Logout")
+ icon: Icons.mobileSignOut
+
+ onClicked: {
+ AuthController.logoutUser()
+ loadPage(loginPage)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ QtObject {
+ id: internal
+
+ function reloadApplication() {
+ if (spamStopper.available) {
+ LibraryController.syncWithServer()
+ UserController.syncWithServer()
+ FolderController.syncWithServer()
+ spamStopper.available = false
+ spamStopper.start()
+ }
+ }
+ }
+
+ Timer {
+ id: spamStopper
+ property bool available: true
+
+ interval: 1000
+ onTriggered: spamStopper.available = true
+ }
+}
diff --git a/src/presentation/mobile/profilePage/MProfilePicture.qml b/src/presentation/mobile/profilePage/MProfilePicture.qml
new file mode 100644
index 000000000..44afd65bc
--- /dev/null
+++ b/src/presentation/mobile/profilePage/MProfilePicture.qml
@@ -0,0 +1,45 @@
+import QtQuick
+import QtQuick.Controls.Material
+import QtQuick.Layouts
+import Librum.style
+import Librum.fonts
+import Librum.controllers
+
+Rectangle {
+ id: root
+ signal clicked
+ property int fontSize: Fonts.size21
+
+ implicitWidth: 44
+ implicitHeight: 44
+ radius: width
+ clip: true
+ antialiasing: true
+ color: UserController.profilePicture.length
+ === 0 ? Style.colorDefaultProfilePicture : "transparent"
+
+ Label {
+ id: initials
+ anchors.centerIn: parent
+ visible: UserController.profilePicture.length === 0
+ text: UserController.name[0].toUpperCase()
+ font.pointSize: root.fontSize
+ font.bold: true
+ color: Style.colorFocusedButtonText
+ }
+
+ Image {
+ id: profileImage
+ visible: UserController.profilePicture.length > 0
+ anchors.centerIn: parent
+ Layout.leftMargin: 18
+ source: UserController.profilePicture
+ sourceSize.height: parent.height
+ fillMode: Image.PreserveAspectFit
+ cache: false
+ }
+
+ TapHandler {
+ onTapped: root.clicked()
+ }
+}
diff --git a/src/presentation/mobile/qmlSourcesMobile.qrc b/src/presentation/mobile/qmlSourcesMobile.qrc
new file mode 100644
index 000000000..a718faab3
--- /dev/null
+++ b/src/presentation/mobile/qmlSourcesMobile.qrc
@@ -0,0 +1,27 @@
+
+
+ main.qml
+ loginPage/MLoginPage.qml
+ startPage/MStartPage.qml
+ modules/CustomComponents/buttons/MButton.qml
+ modules/CustomComponents/qmldir
+ modules/CustomComponents/MLabeledInputBox.qml
+ registerPage/MRegisterPage.qml
+ modules/CustomComponents/MLogo.qml
+ modules/CustomComponents/MCheckBox.qml
+ navbar/MNavbar.qml
+ navbar/MNavbarItem.qml
+ explorePage/MExplorePage.qml
+ profilePage/MProfilePage.qml
+ libraryPage/MLibraryPage.qml
+ profilePage/MProfileCard.qml
+ profilePage/MProfilePicture.qml
+ profilePage/MProfileItem.qml
+ modules/CustomComponents/MFlickWrapper.qml
+ myAccountPage/MMyAccountPage.qml
+ modules/CustomComponents/buttons/MBackButton.qml
+ libraryPage/MBook.qml
+ readingPage/MReadingPage.qml
+ readingPage/MDocumentView.qml
+
+
diff --git a/src/presentation/mobile/readingPage/MDocumentView.qml b/src/presentation/mobile/readingPage/MDocumentView.qml
new file mode 100644
index 000000000..a4e1cc351
--- /dev/null
+++ b/src/presentation/mobile/readingPage/MDocumentView.qml
@@ -0,0 +1,210 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Librum.style
+import Librum.fonts
+import Librum.globals
+import Librum.elements
+import Librum.controllers
+
+Pane {
+ id: root
+ padding: 0
+ background: Rectangle {
+ color: "transparent"
+ }
+
+ // Rectangle {
+ // width: 30
+ // height: 30
+ // color: "green"
+ // x: point1.x
+ // y: point1.y
+ // z: 3
+ // }
+
+ // Rectangle {
+ // width: 30
+ // height: 30
+ // color: "yellow"
+ // x: point2.x
+ // y: point2.y
+ // z: 3
+ // }
+ ListView {
+ id: pageView
+ readonly property int scrollSpeed: 5500
+ property int pageSpacing: pageView.getPageSpacing(BookController.zoom)
+ property int widestItem: 0
+ property real prevZoom: 1
+
+ height: parent.height
+ width: contentWidth <= root.width ? contentWidth : root.width
+ contentWidth: pageView.widestItem
+ anchors.centerIn: parent
+ flickableDirection: Flickable.AutoFlickDirection
+ flickDeceleration: 150000
+ interactive: true
+ clip: true
+ cacheBuffer: 0
+ maximumFlickVelocity: scrollSpeed
+ boundsMovement: Flickable.StopAtBounds
+ boundsBehavior: Flickable.StopAtBounds
+ model: BookController.pageCount
+ spacing: pageSpacing
+ delegate: PageView {
+ id: page
+ pageNumber: modelData
+ bookController: BookController
+ height: implicitHeight
+ width: implicitWidth
+ colorInverted: BookController.colorTheme === "Inverted"
+ includeNewLinesInCopiedText: SettingsController.behaviorSettings.IncludeNewLinesInCopiedText === "ON"
+ anchors.horizontalCenter: if (parent != null)
+ parent.horizontalCenter
+
+ Component.onCompleted: {
+ if (implicitWidth > pageView.contentWidth)
+ pageView.widestItem = page.implicitWidth
+ }
+ }
+
+ PinchArea {
+ property real initialWidth
+ property real initialHeight
+
+ anchors.fill: parent
+ z: 3
+ enabled: true
+
+ onPinchStarted: {
+ initialWidth = pageView.contentWidth
+ initialHeight = pageView.contentHeight
+ }
+
+ onPinchUpdated: pinch => {
+ root.zoom(BookController.zoom * pinch.scale)
+ }
+ }
+
+ // MultiPointTouchArea {
+ // anchors.fill: parent
+ // z: 3
+ // mouseEnabled: false
+ // enabled: true
+ // minimumTouchPoints: 2
+
+ // touchPoints: [
+ // TouchPoint {
+ // id: point1
+ // },
+ // TouchPoint {
+ // id: point2
+ // }
+ // ]
+
+ // onUpdated: {
+
+ // }
+ // }
+ function getPageSpacing(zoom) {
+ return Math.round(SettingsController.appearanceSettings.PageSpacing
+ * (zoom + 0.4 * (1 - zoom)))
+ }
+ }
+
+ ScrollBar {
+ id: verticalScrollbar
+ width: pressed ? 14 : 12
+ hoverEnabled: true
+ active: true
+ policy: ScrollBar.AlwaysOn
+ orientation: Qt.Vertical
+ size: pageView.height / pageView.contentHeight
+ minimumSize: 0.04
+ position: (pageView.contentY - pageView.originY) / pageView.contentHeight
+ onPositionChanged: if (pressed)
+ pageView.contentY = position
+ * pageView.contentHeight + pageView.originY
+ anchors.top: parent.top
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ horizontalPadding: 4
+
+ contentItem: Rectangle {
+ color: Style.colorScrollBarHandle
+ opacity: verticalScrollbar.pressed ? 0.8 : 1
+ radius: 4
+ }
+
+ background: Rectangle {
+ implicitWidth: 26
+ implicitHeight: 200
+ color: verticalScrollbar.pressed
+ || verticalScrollbar.hovered ? Style.colorContainerBackground : "transparent"
+ }
+ }
+
+ ScrollBar {
+ id: horizontalScrollbar
+ height: hovered ? 12 : 10
+ hoverEnabled: true
+ active: true
+ policy: ScrollBar.AlwaysOn
+ visible: pageView.contentWidth > pageView.width
+ orientation: Qt.Horizontal
+ size: pageView.width / pageView.contentWidth
+ minimumSize: 0.04
+ position: (pageView.contentX - pageView.originX) / pageView.contentWidth
+ onPositionChanged: pageView.contentX = position * pageView.contentWidth + pageView.originX
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ horizontalPadding: 4
+
+ contentItem: Rectangle {
+ color: Style.colorScrollBarHandle
+ opacity: horizontalScrollbar.pressed ? 0.8 : 1
+ radius: 4
+ }
+
+ background: Rectangle {
+ implicitWidth: 26
+ implicitHeight: 200
+ color: "transparent"
+ }
+ }
+
+ function getYOffset() {
+ let currentPageHeight = pageView.currentItem.height + pageView.getPageSpacing(
+ BookController.zoom)
+ let currentPos = pageView.contentY - pageView.originY
+ return currentPos - (currentPageHeight * BookController.currentPage)
+ }
+
+ function zoom(newZoomFactor) {
+ // Clamp to max / min zoom factors
+ newZoomFactor = Math.max(0.15, Math.min(newZoomFactor, 10))
+ if (newZoomFactor === BookController.zoom)
+ return
+
+ if (!newZoomFactor > BookController.zoom + 0.5
+ && !newZoomFactor < BookController.zoom - 0.5) {
+ return
+ }
+
+ print("Zooming to " + newZoomFactor)
+
+ let defaultPageHeight = Math.round(
+ pageView.currentItem.height / BookController.zoom)
+ let newPageHeight = Math.round(
+ defaultPageHeight * newZoomFactor) + pageView.getPageSpacing(
+ newZoomFactor)
+
+ let pageOffset = getYOffset()
+
+ BookController.zoom = newZoomFactor
+ pageView.forceLayout()
+ pageView.contentY = 0
+ }
+}
diff --git a/src/presentation/mobile/readingPage/MReadingPage.qml b/src/presentation/mobile/readingPage/MReadingPage.qml
new file mode 100644
index 000000000..9aef4bdfc
--- /dev/null
+++ b/src/presentation/mobile/readingPage/MReadingPage.qml
@@ -0,0 +1,26 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQuick.Window
+import CustomComponents
+import Librum.style
+import Librum.controllers
+import Librum.globals
+
+Page {
+ id: root
+ background: Rectangle {
+ anchors.fill: parent
+ color: Style.colorPageBackground
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+
+ MDocumentView {
+ id: documentView
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ }
+ }
+}
diff --git a/src/presentation/mobile/registerPage/MRegisterPage.qml b/src/presentation/mobile/registerPage/MRegisterPage.qml
new file mode 100644
index 000000000..fc3d95572
--- /dev/null
+++ b/src/presentation/mobile/registerPage/MRegisterPage.qml
@@ -0,0 +1,158 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Librum.style
+import Librum.fonts
+import Librum.controllers
+import CustomComponents
+
+Page {
+ id: page
+ horizontalPadding: 18
+ background: Rectangle {
+ color: Style.colorPageBackground
+ }
+
+ ColumnLayout {
+ id: layout
+ anchors.fill: parent
+ spacing: 0
+
+ Item {
+ Layout.fillHeight: true
+ Layout.minimumHeight: 32
+ Layout.maximumHeight: 52
+ }
+
+ MLogo {
+ id: logo
+ Layout.alignment: Qt.AlignHCenter
+ Layout.preferredWidth: 92
+ Layout.preferredHeight: 92
+ }
+
+ Label {
+ id: welcomeText
+ Layout.alignment: Qt.AlignHCenter
+ Layout.topMargin: 52
+ text: qsTr("Welcome!")
+ color: Style.colorTitle
+ font.weight: Font.Bold
+ font.pointSize: Fonts.size26
+ }
+
+ Label {
+ id: registerText
+ Layout.topMargin: 4
+ Layout.alignment: Qt.AlignHCenter
+ text: qsTr("Create an account and start reading!")
+ color: Style.colorSubtitle
+ font.pointSize: Fonts.size13dot25
+ }
+
+ MLabeledInputBox {
+ id: nameInput
+ Layout.fillWidth: true
+ Layout.preferredHeight: 54
+ Layout.topMargin: 20
+ placeHolderText: qsTr("Name")
+ }
+
+ MLabeledInputBox {
+ id: emailInput
+ Layout.fillWidth: true
+ Layout.preferredHeight: 54
+ Layout.topMargin: 8
+ placeHolderText: qsTr("Email")
+ }
+
+ MLabeledInputBox {
+ id: passwordInput
+ Layout.fillWidth: true
+ Layout.preferredHeight: 54
+ Layout.topMargin: 8
+ isPassword: true
+ placeHolderText: qsTr("Password")
+ textHidden: true
+ }
+
+ MButton {
+ id: registerButton
+ Layout.fillWidth: true
+ Layout.preferredHeight: 54
+ Layout.topMargin: 20
+ borderWidth: 0
+ radius: 8
+ backgroundColor: Style.colorBasePurple
+ fontSize: Fonts.size16
+ opacityOnPressed: 0.85
+ textColor: Style.colorFocusedButtonText
+ fontWeight: Font.Bold
+ text: qsTr("Register")
+
+ onClicked: {
+
+ }
+ }
+
+ Item {
+ id: loginLinkContainer
+ Layout.alignment: Qt.AlignHCenter
+ Layout.topMargin: 10
+ implicitHeight: loginLinkRow.implicitHeight + 8
+ implicitWidth: loginLinkRow.implicitWidth + 8
+
+ RowLayout {
+ id: loginLinkRow
+ anchors.centerIn: parent
+ spacing: 0
+
+ Label {
+ text: qsTr("Already have an account? ")
+ font.pointSize: Fonts.size13
+ opacity: loginLinkArea.pressed ? 0.8 : 1
+ font.weight: Font.Medium
+ color: Style.colorText
+ }
+
+ Label {
+ text: qsTr("Sign in!")
+ font.pointSize: Fonts.size13
+ opacity: loginLinkArea.pressed ? 0.8 : 1
+ font.weight: Font.Medium
+ color: Style.colorBasePurple
+ }
+ }
+
+ TapHandler {
+ id: loginLinkArea
+ onTapped: loadPage(loginPage)
+ }
+ }
+
+ Item {
+ Layout.fillHeight: true
+ }
+
+ Label {
+ textFormat: Text.RichText
+ Layout.alignment: Qt.AlignHCenter
+ Layout.fillWidth: true
+ Layout.bottomMargin: 8
+ wrapMode: Text.WordWrap
+ //: Make sure to translate the following words together to make a logical sentence
+ text: qsTr('By clicking Register, you agree to our') + '
' + qsTr('Terms') + '' + ' '
+ + qsTr('and') + ' ' + qsTr('Privacy Policy') + ''
+ horizontalAlignment: Text.AlignHCenter
+ font.pointSize: Fonts.size12
+ font.weight: Font.Medium
+ color: Style.colorPlaceholderText
+
+ onLinkActivated: link => Qt.openUrlExternally(link)
+ }
+ }
+}
diff --git a/src/presentation/mobile/startPage/MStartPage.qml b/src/presentation/mobile/startPage/MStartPage.qml
new file mode 100644
index 000000000..fcdb33c42
--- /dev/null
+++ b/src/presentation/mobile/startPage/MStartPage.qml
@@ -0,0 +1,250 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Librum.style
+import Librum.fonts
+import Librum.icons
+import Librum.controllers
+import CustomComponents
+
+Page {
+ id: page
+ padding: 0
+ background: Rectangle {
+ color: Style.colorPageBackground
+ }
+
+ Connections {
+ id: proccessLoginResult
+ target: AuthController
+ function onLoginFinished(errorCode, message) {
+ UserController.loadUser(true)
+ }
+ }
+
+ Connections {
+ id: proccessLoadingUserResult
+ target: UserController
+ function onFinishedLoadingUser(success) {
+ if (success)
+ loadPage(homePage, navbar.libraryItem)
+ }
+ }
+
+ Component.onCompleted: {
+ // For some reason this prevents a SEGV. Directly calling the auto login
+ // directly causes the application to crash on startup.
+ autoLoginTimer.start()
+ }
+
+ Timer {
+ id: autoLoginTimer
+ interval: 0
+ onTriggered: AuthController.tryAutomaticLogin()
+ }
+
+ ColumnLayout {
+ id: layout
+ width: parent.width
+ spacing: 0
+
+ SwipeView {
+ id: view
+ Layout.fillWidth: true
+ Layout.preferredHeight: 460
+ currentIndex: 0
+
+ Page {
+ id: firstPage
+ horizontalPadding: 22
+ background: Rectangle {
+ opacity: 0
+ }
+
+ ColumnLayout {
+ width: parent.width
+
+ Image {
+ Layout.fillWidth: true
+ Layout.topMargin: 72
+ Layout.alignment: Qt.AlignHCenter
+ sourceSize.width: 290
+ fillMode: Image.PreserveAspectFit
+ }
+
+ Label {
+ id: title
+ Layout.alignment: Qt.AlignHCenter
+ Layout.fillWidth: true
+ Layout.topMargin: 36
+ Layout.leftMargin: 5
+ Layout.rightMargin: 5
+ text: qsTr("Reading books was never this easy")
+ color: Style.colorText
+ wrapMode: Text.WordWrap
+ horizontalAlignment: Text.AlignHCenter
+ font.bold: true
+ font.pointSize: Fonts.size25
+ }
+
+ Label {
+ id: description
+ Layout.alignment: Qt.AlignHCenter
+ Layout.fillWidth: true
+ Layout.topMargin: 12
+ text: qsTr("Increase your productivity through a simple and modern interface combined with powerful tools.")
+ lineHeight: 1.1
+ color: "#A6A6A6"
+ wrapMode: Text.WordWrap
+ horizontalAlignment: Text.AlignHCenter
+ font.pointSize: Fonts.size15
+ }
+ }
+ }
+
+ Page {
+ id: secondPage
+ horizontalPadding: 22
+ background: Rectangle {
+ opacity: 0
+ }
+
+ ColumnLayout {
+ width: parent.width
+
+ Image {
+ Layout.fillWidth: true
+ Layout.topMargin: 72
+ Layout.alignment: Qt.AlignHCenter
+ sourceSize.width: 290
+ fillMode: Image.PreserveAspectFit
+ }
+
+ Label {
+ Layout.alignment: Qt.AlignHCenter
+ Layout.fillWidth: true
+ Layout.topMargin: 36
+ Layout.leftMargin: 5
+ Layout.rightMargin: 5
+ text: qsTr("Build your own online library")
+ color: Style.colorText
+ wrapMode: Text.WordWrap
+ horizontalAlignment: Text.AlignHCenter
+ font.bold: true
+ font.pointSize: Fonts.size25
+ }
+
+ Label {
+ Layout.alignment: Qt.AlignHCenter
+ Layout.fillWidth: true
+ Layout.topMargin: 12
+ text: qsTr("Add books to build your own online library that you can access at anytime from anywhere.")
+ lineHeight: 1.1
+ color: "#A6A6A6"
+ wrapMode: Text.WordWrap
+ horizontalAlignment: Text.AlignHCenter
+ font.pointSize: Fonts.size15
+ }
+ }
+ }
+
+ Page {
+ id: thirdPage
+ horizontalPadding: 22
+ background: Rectangle {
+ opacity: 0
+ }
+
+ ColumnLayout {
+ width: parent.width
+
+ Image {
+ Layout.fillWidth: true
+ Layout.topMargin: 72
+ Layout.alignment: Qt.AlignHCenter
+ sourceSize.width: 290
+ fillMode: Image.PreserveAspectFit
+ }
+
+ Label {
+ Layout.alignment: Qt.AlignHCenter
+ Layout.fillWidth: true
+ Layout.topMargin: 36
+ Layout.leftMargin: 5
+ Layout.rightMargin: 5
+ text: qsTr("Organize your library your way")
+ color: Style.colorText
+ wrapMode: Text.WordWrap
+ horizontalAlignment: Text.AlignHCenter
+ font.bold: true
+ font.pointSize: Fonts.size25
+ }
+
+ Label {
+ Layout.alignment: Qt.AlignHCenter
+ Layout.fillWidth: true
+ Layout.topMargin: 12
+ text: qsTr("Add your books to collections, tag them and sort them in any way you want.")
+ lineHeight: 1.1
+ color: "#A6A6A6"
+ wrapMode: Text.WordWrap
+ horizontalAlignment: Text.AlignHCenter
+ font.pointSize: Fonts.size15
+ }
+ }
+ }
+ }
+
+ PageIndicator {
+ id: indicator
+ Layout.alignment: Qt.AlignHCenter
+ Layout.topMargin: 22
+ count: view.count
+ currentIndex: view.currentIndex
+ }
+
+ MButton {
+ id: mainButton
+ Layout.fillWidth: true
+ Layout.preferredHeight: 54
+ Layout.topMargin: 32
+ Layout.leftMargin: 22
+ Layout.rightMargin: 22
+ borderWidth: 0
+ radius: 8
+ backgroundColor: Style.colorBasePurple
+ fontSize: Fonts.size16
+ opacityOnPressed: 0.85
+ textColor: Style.colorFocusedButtonText
+ fontWeight: Font.Bold
+ text: view.currentIndex < 2 ? qsTr("Continue") : qsTr("Get started")
+
+ onClicked: {
+ if (view.currentIndex < 2) {
+ view.currentIndex++
+ } else {
+ loadPage(registerPage)
+ }
+ }
+ }
+
+ MButton {
+ id: signInButton
+ Layout.fillWidth: true
+ Layout.preferredHeight: 54
+ Layout.topMargin: 6
+ Layout.leftMargin: 22
+ Layout.rightMargin: 22
+ borderWidth: 0
+ radius: 8
+ backgroundColor: "#F2F2F2"
+ fontSize: Fonts.size16
+ opacityOnPressed: 0.6
+ textColor: Style.colorBasePurple
+ fontWeight: Font.Bold
+ text: qsTr("Sign In")
+
+ onClicked: loadPage(loginPage)
+ }
+ }
+}
diff --git a/src/presentation/qmlSourcesGeneral.qrc b/src/presentation/qmlSourcesGeneral.qrc
new file mode 100644
index 000000000..ebbb7445f
--- /dev/null
+++ b/src/presentation/qmlSourcesGeneral.qrc
@@ -0,0 +1,10 @@
+
+
+ Globals.qml
+ GlobalSettings.qml
+ TranslationsModel.qml
+ FontSheet.qml
+ StyleSheet.qml
+ IconSheet.qml
+
+