diff --git a/ikinuki_client/__main__.py b/ikinuki_client/__main__.py index f82c1e6..6686b9d 100644 --- a/ikinuki_client/__main__.py +++ b/ikinuki_client/__main__.py @@ -3,6 +3,8 @@ from typing import Type import sys from urllib import parse +from collections import defaultdict + from PyQt5.QtWidgets import QApplication from PyQt5.QtQml import qmlRegisterType, QQmlApplicationEngine from PyQt5.QtQuick import QQuickImageProvider @@ -20,11 +22,34 @@ from .qtmpv import MpvObject import requests -class Show(QObject): +class Episode(QObject): def __init__(self, source, parent=None): super().__init__(parent) self._source = source + @pyqtProperty("QString", constant=True) + def title(self): + return self._source["Title"] + + @pyqtProperty(int, constant=True) + def season(self): + return self._source["Season"] + + @pyqtProperty(int, constant=True) + def episode(self): + return self._source["Episode"] + + @pyqtProperty("QString", constant=True) + def filename(self): + return self._source["OriginalFilename"] + + +class Show(QObject): + def __init__(self, source, episodes, parent=None): + super().__init__(parent) + self._source = source + self._episodes = episodes + @pyqtProperty("QString", constant=True) def title(self) -> str: return self._source["title"] @@ -37,10 +62,6 @@ class Show(QObject): def description(self) -> str: return self._source["description"] - @pyqtProperty(int, constant=True) - def episodes(self) -> int: - return self._source["episodes"] - @pyqtProperty(int, constant=True) def watched(self) -> int: return self._source["watched"] @@ -49,6 +70,10 @@ class Show(QObject): def poster(self) -> str: return self._source["poster"] + @pyqtProperty(list, constant=True) + def episodes(self) -> list[Episode]: + return self._episodes + class ProviderImageProvider(QQuickImageProvider): def __init__(self, icon_data): @@ -89,8 +114,20 @@ class Provider(QObject): describe["icon"] ) + def default_val(): + return [] + + episodes: dict = getUrl(self.url, "episodes") + _episodes: defaultdict[str, list[Episode]] = defaultdict(default_val) + + for e in episodes["episodes"]: + _episodes[e["ShowTitle"]].append(Episode(e)) + shows: dict = getUrl(self.url, "shows") - self._shows: dict[int, Show] = {e["id"]: Show(e) for e in shows["data"]} + + self._shows: dict[int, Show] = { + e["id"]: Show(e, _episodes[e["title"]]) for e in shows["data"] + } recently_added: dict = getUrl(self.url, "recently_added") self._recently_added: list[int] = recently_added["data"] @@ -159,11 +196,13 @@ def main(): data_source = DataSource( [ - "http://127.0.0.1:8080/a/", - "http://127.0.0.1:8080/b/", - "http://127.0.0.1:8080/c/", + # "http://127.0.0.1:8080/a/", + # "http://127.0.0.1:8080/b/", + # "http://127.0.0.1:8080/c/", + "http://127.0.0.1:32520/", ] ) + # data_source = DataSource([]) qmlRegisterType(DatabaseType(data_source), "Ikinuki.Client", 1, 0, "Database") # qmlRegisterType(Provider, "Ikinuki.Client", 1, 0, "Provider") diff --git a/layouts/components/BrowserView.qml b/layouts/components/BrowserView.qml index cfca4c6..7058996 100644 --- a/layouts/components/BrowserView.qml +++ b/layouts/components/BrowserView.qml @@ -18,7 +18,7 @@ Row { ContentView { id: view viewSelected: selectedView == 1 - currentIndex: selectedProvider + (browse ? db.Providers.length : 0) + parentIndex: selectedProvider + (browse ? db.Providers.length : 0) providers: db.Providers } function mod(n, m) { diff --git a/layouts/components/BrowserView/ContentView.qml b/layouts/components/BrowserView/ContentView.qml index c364764..ed4b54d 100644 --- a/layouts/components/BrowserView/ContentView.qml +++ b/layouts/components/BrowserView/ContentView.qml @@ -11,8 +11,15 @@ StackLayout { id: tabView property var providers: [] property bool viewSelected - state: viewSelected ? "selected" : "deselected" + property int parentIndex + property int ySelect: 0 + property bool showViewActive: false + + currentIndex: showViewActive ? tabView.children.length - 3 : parentIndex + //currentIndex: showView ? tabView.children.length - 3 : parentIndex + + state: viewSelected ? "selected" : "deselected" width: parent.width * viewSelected ? 0.95 : 0.8 height: parent.height @@ -28,6 +35,10 @@ StackLayout { provider: modelData } } + ShowView { + id: showView + show: providers[0].getShow(providers[0].showsAlphabetic[0]) + } states: [ State { name: "deselected" @@ -54,14 +65,32 @@ StackLayout { } ] + function getCurrentIndex() { + if (showViewActive) { + var x = tabView.children.length - 1; + } else { + var x = currentIndex > (providers.length - 1) ? currentIndex + 1 : currentIndex; + } + return x; + } + Keys.onPressed: (event)=> { - var x = currentIndex > (providers.length - 1) ? currentIndex + 1 : currentIndex; - //tabView.children[currentIndex].Keys.pressed(event); + var x = getCurrentIndex() tabView.children[x].Keys.pressed(event); - if (tabView.children[x].viewExit) { - tabView.children[x].viewExit = false; - parent.browse = false; - parent.selectedView = 0; + if (tabView.children[x].enterShow) { + showViewActive = true; + showView.show = tabView.children[x].enterShowShow; + } else if (tabView.children[x].viewExit) { + if (showViewActive) { + showViewActive = false; + tabView.children[x].xIndex = 0; + tabView.children[x].viewExit = false; + tabView.children[getCurrentIndex()].enterShow = false; + } else { + tabView.children[x].viewExit = false; + parent.browse = false; + parent.selectedView = 0; + } } event.accepted = true; } diff --git a/layouts/components/BrowserView/ContentView/EpisodeView.qml b/layouts/components/BrowserView/ContentView/EpisodeView.qml new file mode 100644 index 0000000..baf23be --- /dev/null +++ b/layouts/components/BrowserView/ContentView/EpisodeView.qml @@ -0,0 +1,34 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 1.4 +import QtQuick.Controls 2.15 + +import Ikinuki.Client 1.0 + +import "./ShowView" + +ScrollView { + id: root + property var episodeModel + property int scrollIndex: 0 + contentHeight: root.height * 0.1 * episodeModel.length + ScrollBar.vertical.position: scrollIndex / (episodeModel.length) + clip: true + Behavior on ScrollBar.vertical.position { + NumberAnimation { + duration: 200 + easing.type: Easing.Linear + } + } + Column { + Repeater { + model: episodeModel + Episode { + episode: modelData + selected: index == xIndex + height: root.height * 0.1 + width: root.width + } + } + } +} diff --git a/layouts/components/BrowserView/ContentView/ProviderBrowse.qml b/layouts/components/BrowserView/ContentView/ProviderBrowse.qml index 66d3070..0a5143d 100644 --- a/layouts/components/BrowserView/ContentView/ProviderBrowse.qml +++ b/layouts/components/BrowserView/ContentView/ProviderBrowse.qml @@ -11,6 +11,8 @@ Rectangle { id: root property var provider property bool viewExit: false + property bool enterShow: false + property var enterShowShow property int currentView: 0 color: "#22282A" Row { @@ -76,6 +78,9 @@ Rectangle { coverGrid.scrollIndex--; } } + } else if (event.key == Qt.Key_Return) { + enterShowShow = root.provider.getShow(root.provider.showsAlphabetic[(coverGrid.xIndex + coverGrid.yIndex * coverGrid.numColumns)]) + enterShow = true; } } else if (currentView == 1) { // alphabet if (event.key == Qt.Key_Left) { diff --git a/layouts/components/BrowserView/ContentView/ProviderHome.qml b/layouts/components/BrowserView/ContentView/ProviderHome.qml index 44d7971..61b03b0 100644 --- a/layouts/components/BrowserView/ContentView/ProviderHome.qml +++ b/layouts/components/BrowserView/ContentView/ProviderHome.qml @@ -11,8 +11,9 @@ Rectangle { id: root property var provider property int ySelect: 0 - property int xSelect: 0 property bool viewExit: false + property bool enterShow: false + property var enterShowShow color: "#22282A" Row { Item { @@ -82,6 +83,9 @@ Rectangle { ySelect++; } else if (event.key == Qt.Key_Up) { ySelect--; + } else if (event.key == Qt.Key_Return) { + enterShowShow = provider.getShow(elementColumn.children[ySelect].showId) + enterShow = true; } event.accepted = true; } diff --git a/layouts/components/BrowserView/ContentView/ShowView.qml b/layouts/components/BrowserView/ContentView/ShowView.qml new file mode 100644 index 0000000..1f21d8d --- /dev/null +++ b/layouts/components/BrowserView/ContentView/ShowView.qml @@ -0,0 +1,76 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 1.4 +import QtQuick.Controls 2.15 + +import Ikinuki.Client 1.0 + +import "./ShowView" + +Rectangle { + property var show + property bool viewExit: false + property bool enterShow: false + property int xIndex: 0 + color: "#22282A" + + Column { + anchors.fill: parent + anchors.leftMargin: parent.width * 0.01 + anchors.rightMargin: parent.width * 0.01 + anchors.topMargin: parent.height * 0.05 + anchors.bottomMargin: parent.height * 0.01 + Text { // header + height: parent.height * 0.1 + width: parent.width + text: show.title + font.pointSize: 20 + color: "#cdd7d9" + } + Row { // main view + height: parent.height * 0.8 + width: parent.width + spacing: parent.width * 0.01 + Item { + height: parent.height + width: height * 0.68 + Image { + source: show.poster + anchors.fill: parent + mipmap: true + } + } + EpisodeView { + id: episodeView + height: parent.height + width: parent.width * 0.65 + episodeModel: show.episodes + } + } + Item { + height: parent.height * 0.1 + width: parent.width + } + } + + Keys.onPressed: (event)=> { + if (event.key == Qt.Key_Up) { + if (xIndex > 0) { + xIndex--; + if (episodeView.scrollIndex > xIndex) { + episodeView.scrollIndex--; + } + } + } else if (event.key == Qt.Key_Down) { + if (xIndex < show.episodes.length - 1) { + if (episodeView.scrollIndex < (xIndex - 8)) { + episodeView.scrollIndex++; + } + xIndex++; + } + } else if (event.key == Qt.Key_Escape) { + viewExit = true; + } + event.accepted = true; + } +} diff --git a/layouts/components/BrowserView/ContentView/ShowView/Episode.qml b/layouts/components/BrowserView/ContentView/ShowView/Episode.qml new file mode 100644 index 0000000..1626b64 --- /dev/null +++ b/layouts/components/BrowserView/ContentView/ShowView/Episode.qml @@ -0,0 +1,49 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 1.4 +import QtQuick.Controls 2.15 + +import Ikinuki.Client 1.0 + +Item { + property var episode + property bool selected + + Rectangle { + anchors.fill: parent + color: "white" + visible: selected + radius: 10 + } + Row { + anchors.fill: parent + Item { + height: parent.height + width: parent.width * 0.04 + } + Column { + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: 4 + Text { + text: { + if (modelData.season == 0) { + return "S" + modelData.episode + " " + modelData.title + } else { + return String(modelData.episode).padStart(2, "0") + ". " + modelData.title + } + } + font.pointSize: 20 + color: selected ? "#3c3c3c" : "#99afb4" + } + Item { + height: parent.height * 0.1 + width: parent.width + } + Text { + text: " air date" + font.pointSize: 12 + color: selected ? "#3c3c3c" : "#99afb4" + } + } + } +} diff --git a/layouts/components/BrowserView/ContentView/ShowView/qmldir b/layouts/components/BrowserView/ContentView/ShowView/qmldir new file mode 100644 index 0000000..e69de29