From 3cd1fd3e231c87aa06fddfc5079ac73b39a760c8 Mon Sep 17 00:00:00 2001 From: Henk Kalkwater Date: Fri, 19 Mar 2021 20:57:04 +0100 Subject: [PATCH] Update UI to bare minimum to allow for core lib testing --- CMakeLists.txt | 4 ++ core/src/jellyfinapiclient.cpp | 8 ++- core/src/jellyfindeviceprofile.cpp | 18 ++--- qtquick/qml.qrc | 2 + qtquick/qml/main.qml | 5 +- qtquick/qml/pages/DetailPage.qml | 37 ++++++++++ qtquick/qml/pages/MainPage.qml | 99 +++++++++++++++++++++++++++ qtquick/qml/pages/setup/LoginPage.qml | 45 ++++++++++++ 8 files changed, 206 insertions(+), 12 deletions(-) create mode 100644 qtquick/qml/pages/DetailPage.qml create mode 100644 qtquick/qml/pages/MainPage.qml diff --git a/CMakeLists.txt b/CMakeLists.txt index 3223568..01ad104 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,10 @@ cmake_policy(SET CMP0048 NEW) option(SAILFISHOS "Build SailfishOS version of application") option(PLATFORM_QTQUICK "Build QtQuick version of application") +if (NOT SAILFIN_VERSION) + set(SAILFIN_VERSION "1.0.0") +endif() + if(SAILFISHOS) # Hardcode this less? set(CMAKE_INSTALL_LIBDIR "${CMAKE_INSTALL_PREFIX}/share/harbour-sailfin/lib") diff --git a/core/src/jellyfinapiclient.cpp b/core/src/jellyfinapiclient.cpp index 2fbfdab..196a989 100644 --- a/core/src/jellyfinapiclient.cpp +++ b/core/src/jellyfinapiclient.cpp @@ -184,7 +184,7 @@ void ApiClient::authenticate(QString username, QString password, bool storeCrede requestData["Username"] = username; requestData["Pw"] = password; - QNetworkReply *rep = post("/Users/Authenticatebyname", QJsonDocument(requestData)); + QNetworkReply *rep = post("/Users/authenticatebyname", QJsonDocument(requestData)); connect(rep, &QNetworkReply::finished, this, [rep, username, storeCredentials, this]() { int status = rep->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); qDebug() << "Got reply with status code " << status; @@ -202,6 +202,12 @@ void ApiClient::authenticate(QString username, QString password, bool storeCrede if (storeCredentials) { m_credManager->store(this->m_baseUrl, this->m_userId, this->m_token); } + } else if(status >= 400 && status < 500) { + if (status == 401) { + emit authenticationError(ApiError::INVALID_PASSWORD); + } else { + emit authenticationError(ApiError::UNEXPECTED_STATUS); + } } rep->deleteLater(); }); diff --git a/core/src/jellyfindeviceprofile.cpp b/core/src/jellyfindeviceprofile.cpp index 9b7342d..a70c18b 100644 --- a/core/src/jellyfindeviceprofile.cpp +++ b/core/src/jellyfindeviceprofile.cpp @@ -85,7 +85,7 @@ QJsonObject DeviceProfile::generateProfile() { QJsonObject { JsonPair("Property", "IsSecondaryAudio"), JsonPair("Condition", "Equals"), - JsonPair("Value", false), + JsonPair("Value", "false"), JsonPair("IsRequired", false) } }), @@ -97,7 +97,7 @@ QJsonObject DeviceProfile::generateProfile() { QJsonObject { JsonPair("Property", "IsAnamorphic"), JsonPair("Condition", "NotEquals"), - JsonPair("Value", true), + JsonPair("Value", "true"), JsonPair("IsRequired", false) }, QJsonObject { @@ -109,13 +109,13 @@ QJsonObject DeviceProfile::generateProfile() { QJsonObject { JsonPair("Property", "VideoLevel"), JsonPair("Condition", "LessThanEqual"), - JsonPair("Value", 51), + JsonPair("Value", "51"), JsonPair("IsRequired", false) }, QJsonObject { JsonPair("Property", "IsInterlaced"), JsonPair("Condition", "NotEquals"), - JsonPair("Value", true), + JsonPair("Value", "true"), JsonPair("IsRequired", false) } }), @@ -130,8 +130,8 @@ QJsonObject DeviceProfile::generateProfile() { transcoding1["BreakOnNonKeyFrames"] =true; transcoding1["Container"] = "ts"; transcoding1["Context"] = "Streaming"; - transcoding1["MaxAudioChannels"] = 2; - transcoding1["MinSegments"] = 1; + transcoding1["MaxAudioChannels"] = "2"; + transcoding1["MinSegments"] = "1"; transcoding1["Protocol"] = "hls"; transcoding1["Type"] = "Audio"; transcodingProfiles.append(transcoding1); @@ -142,7 +142,7 @@ QJsonObject DeviceProfile::generateProfile() { JsonPair("BreakOnNonKeyFrames", true), JsonPair("Container", "ts"), JsonPair("Context", "Streaming"), - JsonPair("MaxAudioChannels", 2), + JsonPair("MaxAudioChannels", "2"), JsonPair("MinSegments", 1), JsonPair("Protocol", "hls"), JsonPair("Type", "Video"), @@ -168,8 +168,8 @@ QJsonObject DeviceProfile::generateProfile() { JsonPair("VideoCodec", hlsVideoCodecs.join(",")), JsonPair("Context", "Streaming"), JsonPair("Protocol", "hls"), - JsonPair("MaxAudioChannels", 2), - JsonPair("MinSegments", 1), + JsonPair("MaxAudioChannels", "2"), + JsonPair("MinSegments", "1"), JsonPair("BreakOnNonKeyFrames", true) }); } diff --git a/qtquick/qml.qrc b/qtquick/qml.qrc index 74e3502..6307c82 100644 --- a/qtquick/qml.qrc +++ b/qtquick/qml.qrc @@ -14,5 +14,7 @@ qml/components/+sailfinstyle/Background.qml qml/components/BusyDialog.qml qml/pages/setup/LoginPage.qml + qml/pages/MainPage.qml + qml/pages/DetailPage.qml diff --git a/qtquick/qml/main.qml b/qtquick/qml/main.qml index 90f4470..0418b9f 100644 --- a/qtquick/qml/main.qml +++ b/qtquick/qml/main.qml @@ -1,6 +1,5 @@ import QtQuick 2.12 import QtQuick.Controls 2.12 -import QtGraphicalEffects 1.12 import QtQuick.Window 2.12 import nl.netsoj.chris.Jellyfin 1.0 @@ -31,11 +30,13 @@ ApplicationWindow { } _oldDepth = depth } + initialItem: Qt.resolvedUrl("pages/MainPage.qml") + Keys.onEscapePressed: pop() } Connections { target: ApiClient - onSetupRequired: pageStack.push(Qt.resolvedUrl("pages/setup/ServerSelectPage.qml")); + onSetupRequired: pageStack.replace(Qt.resolvedUrl("pages/setup/ServerSelectPage.qml")); } Component.onCompleted: { diff --git a/qtquick/qml/pages/DetailPage.qml b/qtquick/qml/pages/DetailPage.qml new file mode 100644 index 0000000..bbff247 --- /dev/null +++ b/qtquick/qml/pages/DetailPage.qml @@ -0,0 +1,37 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Window 2.12 + +import nl.netsoj.chris.Jellyfin 1.0 + +import "../components" +import "../.." + +Page { + property bool _modelsLoaded: false + property StackView stackView: StackView.view + property string itemId + property alias jellyfinItem: jellyfinItem + header: ToolBar { + Label { + anchors.horizontalCenter: parent.horizontalCenter + font.pixelSize: SailfinStyle.fontSizeLarge + text: jellyfinItem.name + } + } + + MouseArea { + anchors.fill: parent + onClicked: stackView.pop() + } + JellyfinItem { + id: jellyfinItem + jellyfinId: itemId + apiClient: ApiClient + } + + Image { + anchors.centerIn: parent + source: ApiClient.baseUrl + "/Items/" + itemId + "/Images/Primary?tag=" + jellyfinItem.tag + } +} diff --git a/qtquick/qml/pages/MainPage.qml b/qtquick/qml/pages/MainPage.qml new file mode 100644 index 0000000..d373ad2 --- /dev/null +++ b/qtquick/qml/pages/MainPage.qml @@ -0,0 +1,99 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Window 2.12 + +import nl.netsoj.chris.Jellyfin 1.0 + +import "../components" +import "../.." + +Page { + property bool _modelsLoaded: false + property StackView stackView: StackView.view + header: ToolBar { + Label { + anchors.horizontalCenter: parent.horizontalCenter + font.pixelSize: SailfinStyle.fontSizeLarge + text: qsTr("Main page") + } + } + + UserViewModel { + id: mediaLibraryModel + apiClient: ApiClient + } + + ScrollView { + anchors.fill: parent + contentHeight: content.height + Column { + id: content + width: parent.width + Repeater { + model: mediaLibraryModel + Column { + width: parent.width + UserItemLatestModel { + id: userItemModel + apiClient: ApiClient + parentId: model.id + limit: 16 + } + Label { + text: model.name + } + + ListView { + width: parent.width + height: SailfinStyle.unit * 20 + orientation: ListView.Horizontal + model: userItemModel + delegate: ItemDelegate { + width: 12 * SailfinStyle.unit + height: 20 * SailfinStyle.unit + Image { + anchors.fill: parent + source: ApiClient.baseUrl + "/Items/" + model.id + "/Images/Primary?tag=" + model.tag + } + Label { + anchors.left: parent.left + anchors.top: parent.top + anchors.right: parent.right + text: model.name + } + onClicked: stackView.push(Qt.resolvedUrl("DetailPage.qml"), {"itemId": model.id}) + } + } + Connections { + target: mediaLibraryModel + onStatusChanged: { + if (mediaLibraryModel.status == UserItemModel.Ready) { + userItemModel.reload() + } + } + } + } + } + } + } + + /** + * Loads models if not laoded. Set force to true to reload models + * even if loaded. + */ + function loadModels(force) { + if (force || (ApiClient.authenticated && !_modelsLoaded)) { + _modelsLoaded = true; + mediaLibraryModel.reload() + } + } + + Connections { + target: ApiClient + onAuthenticatedChanged: { + if (authenticated) { + loadModels(false) + } + } + } +} diff --git a/qtquick/qml/pages/setup/LoginPage.qml b/qtquick/qml/pages/setup/LoginPage.qml index 704515d..c09e3aa 100644 --- a/qtquick/qml/pages/setup/LoginPage.qml +++ b/qtquick/qml/pages/setup/LoginPage.qml @@ -1,10 +1,14 @@ import QtQuick 2.12 import QtQuick.Controls 2.12 +import nl.netsoj.chris.Jellyfin 1.0 + +import "../../components" import "../../.." Page { property string loginMessage + property StackView stackView: StackView.view header: ToolBar { Label { anchors.horizontalCenter: parent.horizontalCenter @@ -20,11 +24,27 @@ Page { TextField { id: usernameField width: parent.width + placeholderText: qsTr("Username") EnterKey.type: Qt.EnterKeyNext } TextField { id: passwordField width: parent.width + placeholderText: qsTr("Password") + echoMode: TextInput.Password + } + + Label { + id: loginError + width: parent.width + wrapMode: Text.WordWrap + text: qsTr("Invalid username/password") + visible: false + } + + Button { + text: qsTr("Login") + onClicked: login() } Label { width: parent.width @@ -32,4 +52,29 @@ Page { wrapMode: Text.WordWrap } } + + BusyDialog { + id: busyDialog + anchors.centerIn: Overlay.overlay + title: qsTr("Logging in as %1").arg(usernameField.text) + } + + function login() { + busyDialog.open() + ApiClient.authenticate(usernameField.text, passwordField.text, true) + } + + Connections { + target: ApiClient + onAuthenticatedChanged: { + busyDialog.close() + if (authenticated) { + stackView.replace(null, Qt.resolvedUrl("../MainPage.qml"), {}) + } + } + onAuthenticationError: { + busyDialog.close() + loginError.visible = true + } + } }