diff --git a/common.pri b/common.pri
new file mode 100644
index 0000000..17cb998
--- /dev/null
+++ b/common.pri
@@ -0,0 +1,17 @@
+QT += qml quick network dbus
+CONFIG += c++11
+
+SOURCES += \
+ src/main.cpp \
+ src/ircchat.cpp \
+ src/tools.cpp \
+ src/qmlsettings.cpp \
+ src/message.cpp \
+ src/messagelistmodel.cpp
+
+HEADERS += \
+ src/ircchat.h \
+ src/tools.h \
+ src/qmlsettings.h \
+ src/message.h \
+ src/messagelistmodel.h
diff --git a/harbour-twitchtube.pro b/harbour-twitchtube.pro
index c34cf07..5eb1e9a 100644
--- a/harbour-twitchtube.pro
+++ b/harbour-twitchtube.pro
@@ -12,65 +12,34 @@
# The name of your application
TARGET = harbour-twitchtube
-CONFIG += sailfishapp c++11
+CONFIG += sailfishapp
-QT += network dbus
+DEFINES += OS_SAILFISH
-icons.path = /usr/share/icons/hicolor
-icons.files = icons/*
-INSTALLS += icons
+include(common.pri)
-SOURCES += \
- src/harbour-twitchtube.cpp \
- src/ircchat.cpp \
- src/tools.cpp \
- src/qmlsettings.cpp \
- src/message.cpp \
- src/messagelistmodel.cpp
+RESOURCES += sailfish-ui/TwitchTube.qrc
-OTHER_FILES += \
- translations/*.ts \
- qml/pages/GamesPage.qml \
- qml/pages/ChannelsPage.qml \
- qml/pages/StreamPage.qml \
- qml/js/httphelper.js \
- qml/pages/SettingsPage.qml \
- qml/pages/SearchPage.qml \
- qml/pages/LoginPage.qml \
- qml/pages/FollowedPage.qml \
- qml/harbour-twitchtube.qml \
- harbour-twitchtube.desktop \
- rpm/harbour-twitchtube.spec \
- rpm/harbour-twitchtube.yaml \
- qml/pages/elements/Categories.qml \
- qml/pages/QualityChooserPage.qml \
- rpm/harbour-twitchtube.changes \
- qml/images/heart.png \
- qml/images/icon.png \
- qml/pages/elements/GamesGrid.qml \
- qml/pages/elements/ChannelsGrid.qml \
- qml/pages/GameChannelsPage.qml \
- qml/pages/FollowedGamesPage.qml \
- qml/cover/NavigationCover.qml \
- qml/cover/StreamCover.qml \
- qml/images/heart_crossed.png
+QML_FILES += $$files(sailfish-ui/*.qml,true) \
+ $$files(sailfish-ui/*.js,true) \
+ $$files(sailfish-ui/*.png,true)
+
+CONF_FILES += rpm/harbour-twitchtube.spec \
+ rpm/harbour-twitchtube.yaml \
+ rpm/harbour-twitchtube.changes
+
+TRANSLATIONS += $$files(translations/*.ts)
+
+OTHER_FILES += $${CONF_FILES} \
+ $${QML_FILES} \
+ harbour-twitchtube.desktop
+
+SAILFISHAPP_ICONS = 86x86 108x108 128x128 256x256
# to disable building translations every time, comment out the
# following CONFIG line
CONFIG += sailfishapp_i18n
-#TRANSLATIONS += translations/harbour-twitchtube-ru.ts
-
-HEADERS += \
- src/ircchat.h \
- src/tools.h \
- src/qmlsettings.h \
- src/message.h \
- src/messagelistmodel.h
DISTFILES += \
- icons/108x108/apps/harbour-twitchtube.png \
- icons/128x128/apps/harbour-twitchtube.png \
- icons/256x256/apps/harbour-twitchtube.png \
- icons/86x86/apps/harbour-twitchtube.png \
- qml/pages/elements/GridWrapper.qml
+ sailfish-ui/Main.qml
diff --git a/icons/108x108/apps/harbour-twitchtube.png b/icons/108x108/harbour-twitchtube.png
similarity index 100%
rename from icons/108x108/apps/harbour-twitchtube.png
rename to icons/108x108/harbour-twitchtube.png
diff --git a/icons/128x128/apps/harbour-twitchtube.png b/icons/128x128/harbour-twitchtube.png
similarity index 100%
rename from icons/128x128/apps/harbour-twitchtube.png
rename to icons/128x128/harbour-twitchtube.png
diff --git a/icons/256x256/apps/harbour-twitchtube.png b/icons/256x256/harbour-twitchtube.png
similarity index 100%
rename from icons/256x256/apps/harbour-twitchtube.png
rename to icons/256x256/harbour-twitchtube.png
diff --git a/icons/86x86/apps/harbour-twitchtube.png b/icons/86x86/harbour-twitchtube.png
similarity index 100%
rename from icons/86x86/apps/harbour-twitchtube.png
rename to icons/86x86/harbour-twitchtube.png
diff --git a/rpm/harbour-twitchtube.spec b/rpm/harbour-twitchtube.spec
index cc79ef3..1667330 100644
--- a/rpm/harbour-twitchtube.spec
+++ b/rpm/harbour-twitchtube.spec
@@ -69,9 +69,6 @@ desktop-file-install --delete-original \
%{_bindir}
%{_datadir}/%{name}
%{_datadir}/applications/%{name}.desktop
-%{_datadir}/icons/hicolor/86x86/apps/%{name}.png
-%{_datadir}/icons/hicolor/108x108/apps/%{name}.png
-%{_datadir}/icons/hicolor/128x128/apps/%{name}.png
-%{_datadir}/icons/hicolor/256x256/apps/%{name}.png
+%{_datadir}/icons/hicolor/*/apps/%{name}.png
# >> files
# << files
diff --git a/rpm/harbour-twitchtube.yaml b/rpm/harbour-twitchtube.yaml
index e108d94..66329c8 100644
--- a/rpm/harbour-twitchtube.yaml
+++ b/rpm/harbour-twitchtube.yaml
@@ -42,10 +42,7 @@ Files:
- '%{_bindir}'
- '%{_datadir}/%{name}'
- '%{_datadir}/applications/%{name}.desktop'
- - '%{_datadir}/icons/hicolor/86x86/apps/%{name}.png'
- - '%{_datadir}/icons/hicolor/108x108/apps/%{name}.png'
- - '%{_datadir}/icons/hicolor/128x128/apps/%{name}.png'
- - '%{_datadir}/icons/hicolor/256x256/apps/%{name}.png'
+ - '%{_datadir}/icons/hicolor/*/apps/%{name}.png'
# For more information about yaml and what's supported in Sailfish OS
# build system, please see https://wiki.merproject.org/wiki/Spectacle
diff --git a/sailfish-icon.svg b/sailfish-icon.svg
new file mode 100644
index 0000000..c97c2f4
--- /dev/null
+++ b/sailfish-icon.svg
@@ -0,0 +1,229 @@
+
+
+
+
\ No newline at end of file
diff --git a/qml/harbour-twitchtube.qml b/sailfish-ui/Main.qml
similarity index 100%
rename from qml/harbour-twitchtube.qml
rename to sailfish-ui/Main.qml
diff --git a/sailfish-ui/TwitchTube.qrc b/sailfish-ui/TwitchTube.qrc
new file mode 100755
index 0000000..8848bf6
--- /dev/null
+++ b/sailfish-ui/TwitchTube.qrc
@@ -0,0 +1,26 @@
+
+
+ Main.qml
+ pages/ChannelsPage.qml
+ pages/GameChannelsPage.qml
+ pages/GamesPage.qml
+ pages/StreamPage.qml
+ pages/FollowedPage.qml
+ pages/LoginPage.qml
+ pages/QualityChooserPage.qml
+ pages/SearchPage.qml
+ pages/SettingsPage.qml
+ pages/FollowedGamesPage.qml
+ pages/elements/Categories.qml
+ pages/elements/ChannelsGrid.qml
+ pages/elements/GamesGrid.qml
+ pages/elements/GridWrapper.qml
+ cover/NavigationCover.qml
+ cover/StreamCover.qml
+ js/httphelper.js
+ images/heart.png
+ images/heart_crossed.png
+ images/icon.png
+
+
+
diff --git a/qml/cover/NavigationCover.qml b/sailfish-ui/cover/NavigationCover.qml
similarity index 100%
rename from qml/cover/NavigationCover.qml
rename to sailfish-ui/cover/NavigationCover.qml
diff --git a/qml/cover/StreamCover.qml b/sailfish-ui/cover/StreamCover.qml
similarity index 100%
rename from qml/cover/StreamCover.qml
rename to sailfish-ui/cover/StreamCover.qml
diff --git a/qml/images/heart.png b/sailfish-ui/images/heart.png
similarity index 100%
rename from qml/images/heart.png
rename to sailfish-ui/images/heart.png
diff --git a/heart.svg b/sailfish-ui/images/heart.svg
similarity index 79%
rename from heart.svg
rename to sailfish-ui/images/heart.svg
index 68293cf..58cf121 100644
--- a/heart.svg
+++ b/sailfish-ui/images/heart.svg
@@ -21,7 +21,14 @@
inkscape:export-ydpi="22.5">image/svg+xml
+
+
+
+
+
+
+
+
#endif
+#ifdef OS_SAILFISH
#include
+#endif
#include
#include
@@ -76,21 +78,32 @@ int main(int argc, char *argv[])
//
// To display the view, call "show()" (will show fullscreen on device).
+#ifdef OS_SAILFISH
QGuiApplication *app(SailfishApp::application(argc, argv));
QCoreApplication::setOrganizationName("harbour-twitchtube");
QCoreApplication::setApplicationName("harbour-twitchtube");
-
qmlRegisterType("harbour.twitchtube.ircchat", 1, 0, "IrcChat");
qmlRegisterType("harbour.twitchtube.ircchat", 1, 0, "MessageListModel");
qmlRegisterType("harbour.twitchtube.settings", 1, 0, "Setting");
QQuickView *view(SailfishApp::createView());
+#else
+ QGuiApplication *app = new QGuiApplication(argc, argv);
+ QCoreApplication::setOrganizationName("twitchtube.aldrog");
+ QCoreApplication::setApplicationName("twitchtube.aldrog");
+ qmlRegisterType("aldrog.twitchtube.ircchat", 1, 0, "IrcChat");
+ qmlRegisterType("aldrog.twitchtube.ircchat", 1, 0, "MessageListModel");
+ qmlRegisterType("aldrog.twitchtube.settings", 1, 0, "Setting");
+
+ QQuickView *view = new QQuickView();
+#endif
registerSettings(view);
Tools *tools = new Tools();
view->rootContext()->setContextProperty("cpptools", tools);
- view->setSource(SailfishApp::pathTo("qml/harbour-twitchtube.qml"));
+ view->setSource(QUrl("qrc:///Main.qml"));
+ view->setResizeMode(QQuickView::SizeRootObjectToView);
view->show();
return app->exec();
}
diff --git a/src/tools.cpp b/src/tools.cpp
old mode 100644
new mode 100755
index 5b959a9..67056b2
--- a/src/tools.cpp
+++ b/src/tools.cpp
@@ -22,17 +22,24 @@
#include
#include
#include
+
+#ifdef OS_SAILFISH
#include
+#endif
Tools::Tools(QObject *parent) :
- QObject(parent),
- mceReqInterface("com.nokia.mce",
+ QObject(parent)
+#ifdef OS_SAILFISH
+ , mceReqInterface("com.nokia.mce",
"/com/nokia/mce/request",
"com.nokia.mce.request",
QDBusConnection::connectToBus(QDBusConnection::SystemBus, "system"))
+#endif
{
+#ifdef OS_SAILFISH
pauseRefresher = new QTimer();
connect(pauseRefresher, SIGNAL(timeout()), this, SLOT(refreshPause()));
+#endif
}
Tools::~Tools() { }
@@ -45,6 +52,8 @@ Tools::~Tools() { }
int Tools::clearCookies() {
QStringList dataPaths = QStandardPaths::standardLocations(QStandardPaths::DataLocation);
if(dataPaths.size()) {
+ qDebug() << QDir(dataPaths[0]).entryList();
+#ifdef OS_SAILFISH
QDir webData(QDir(dataPaths.at(0)).filePath(".QtWebKit"));
if(webData.exists()) {
if(webData.removeRecursively())
@@ -54,10 +63,22 @@ int Tools::clearCookies() {
}
else
return 1;
+#elif OS_UBUNTU
+ QDir webData(QDir(dataPaths.at(0)));
+ if(webData.exists()) {
+ if(webData.removeRecursively())
+ return 0;
+ else
+ return -1;
+ }
+ else
+ return 1;
+#endif
}
return -2;
}
+#ifdef OS_SAILFISH
// true - screen blanks (default)
// false - no blanking
void Tools::setBlankingMode(bool state)
@@ -80,3 +101,4 @@ void Tools::refreshPause() {
mceReqInterface.call(QLatin1String("req_display_blanking_pause"));
}
+#endif
diff --git a/src/tools.h b/src/tools.h
old mode 100644
new mode 100755
index a64cf5c..9c19a95
--- a/src/tools.h
+++ b/src/tools.h
@@ -22,10 +22,13 @@
#include
#include
+
+#ifdef OS_SAILFISH
#include
#include
const int PAUSE_PERIOD = 50000; //ms
+#endif
class Tools : public QObject
{
@@ -35,12 +38,18 @@ class Tools : public QObject
~Tools();
Q_INVOKABLE int clearCookies();
+#ifdef OS_SAILFISH
Q_INVOKABLE void setBlankingMode(bool state);
+#endif
public slots:
+#ifdef OS_SAILFISH
void refreshPause();
+#endif
protected:
+#ifdef OS_SAILFISH
QDBusInterface mceReqInterface;
QTimer* pauseRefresher;
+#endif
};
#endif // TOOLS_H
diff --git a/translations/harbour-twitchtube-ru.ts b/translations/harbour-twitchtube-ru.ts
deleted file mode 100644
index 8b738fc..0000000
--- a/translations/harbour-twitchtube-ru.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-
-
-
-
- ChannelsPage
-
-
- Настройки
-
-
-
- Поиск
-
-
-
- Любимые
-
-
-
- Игры
-
-
-
- Каналы
-
-
-
- Загрузить ещё
-
-
-
- Не точно
- Прямые трансляции
-
-
-
- GamesPage
-
-
- Настройки
-
-
-
- Поиск
-
-
-
- Любимые
-
-
-
- Каналы
-
-
-
- Загрузить ещё
-
-
-
- Популярные игры
-
-
-
- SearchPage
-
-
- Поиск каналов
-
-
-
- Настройки
-
-
-
- Любимые
-
-
-
- Игры
-
-
-
- Каналы
-
-
-
- SettingsPage
-
-
- Настройки Twitch
-
-
-
- Применить
-
-
-
- Качество изображений игр
-
-
-
- Высокое
-
-
-
- Среднее
-
-
-
- Низкое
-
-
-
- Качество изображений трансляций
-
-
-
diff --git a/ubuntu-icon.svg b/ubuntu-icon.svg
new file mode 100644
index 0000000..6264d7a
--- /dev/null
+++ b/ubuntu-icon.svg
@@ -0,0 +1,231 @@
+
+
+
+
\ No newline at end of file
diff --git a/ubuntu-twitchtube.pro b/ubuntu-twitchtube.pro
new file mode 100644
index 0000000..50b3290
--- /dev/null
+++ b/ubuntu-twitchtube.pro
@@ -0,0 +1,44 @@
+TEMPLATE = app
+TARGET = TwitchTube
+
+load(ubuntu-click)
+
+include(common.pri)
+
+# specify the manifest file, this file is required for click
+# packaging and for the IDE to create runconfigurations
+UBUNTU_MANIFEST_FILE=ubuntu/manifest.json.in
+
+# specify translation domain, this must be equal with the
+# app name in the manifest file
+UBUNTU_TRANSLATION_DOMAIN="twitchtube.aldrog"
+
+DEFINES += OS_UBUNTU
+
+RESOURCES += ubuntu-ui/TwitchTube.qrc
+
+QML_FILES += $$files(ubuntu-ui/*.qml,true) \
+ $$files(ubuntu-ui/*.js,true)
+
+CONF_FILES += ubuntu/TwitchTube.apparmor \
+ ubuntu/TwitchTube.png
+
+OTHER_FILES += $${CONF_FILES} \
+ $${QML_FILES} \
+ ubuntu/TwitchTube.desktop
+
+#specify where the config files are installed to
+config_files.path = /TwitchTube
+config_files.files += $${CONF_FILES}
+INSTALLS += config_files
+
+#install the desktop file, a translated version is
+#automatically created in the build directory
+desktop_file.path = /TwitchTube
+desktop_file.files = ubuntu/TwitchTube.desktop
+desktop_file.CONFIG += no_check_exist
+INSTALLS+=desktop_file
+
+# Default rules for deployment.
+target.path = $${UBUNTU_CLICK_BINARY_PATH}
+INSTALLS += target
diff --git a/ubuntu-ui/Main.qml b/ubuntu-ui/Main.qml
new file mode 100644
index 0000000..bf7c35f
--- /dev/null
+++ b/ubuntu-ui/Main.qml
@@ -0,0 +1,63 @@
+/*
+ * Copyright © 2015-2016 Andrew Penkrat
+ *
+ * This file is part of TwitchTube.
+ *
+ * TwitchTube is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * TwitchTube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TwitchTube. If not, see .
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+import "pages"
+import "js/httphelper.js" as HTTP
+
+MainView {
+ id: mainWindow
+
+ property string username
+
+ signal userChanged
+
+ // objectName for functional testing purposes (autopilot-qt5)
+ objectName: "mainView"
+
+ // Note! applicationName needs to match the "name" field of the click manifest
+ applicationName: "twitchtube.aldrog"
+
+ width: units.gu(100)
+ height: units.gu(75)
+
+ Component.onCompleted: {
+ if(authToken.value) {
+ HTTP.getRequest("https://api.twitch.tv/kraken/user?oauth_token=" + authToken.value, function(data) {
+ if(data) {
+ var user = JSON.parse(data)
+ username = user.name
+ console.log("Successfully received username")
+ }
+ })
+ }
+
+ pageStack.push(startingPage)
+ }
+
+ PageStack {
+ id: pageStack
+ }
+
+ GamesPage {
+ id: startingPage
+ visible: false
+ }
+}
diff --git a/ubuntu-ui/TwitchTube.qrc b/ubuntu-ui/TwitchTube.qrc
new file mode 100755
index 0000000..8b538fd
--- /dev/null
+++ b/ubuntu-ui/TwitchTube.qrc
@@ -0,0 +1,23 @@
+
+
+ Main.qml
+ pages/ChannelsPage.qml
+ pages/GameChannelsPage.qml
+ pages/GamesPage.qml
+ pages/FollowedPage.qml
+ pages/FollowedGamesPage.qml
+ pages/SearchPage.qml
+ pages/StreamPage.qml
+ pages/QualityChooserPage.qml
+ pages/SettingsPage.qml
+ pages/LoginPage.qml
+ pages/elements/Categories.qml
+ pages/elements/ChannelsGrid.qml
+ pages/elements/GamesGrid.qml
+ pages/elements/GridWrapper.qml
+ js/httphelper.js
+ images/heart.png
+ images/heart_crossed.png
+
+
+
diff --git a/ubuntu-ui/cover/NavigationCover.qml b/ubuntu-ui/cover/NavigationCover.qml
new file mode 100755
index 0000000..7b1f7ab
--- /dev/null
+++ b/ubuntu-ui/cover/NavigationCover.qml
@@ -0,0 +1,30 @@
+/*
+ * Copyright © 2015-2016 Andrew Penkrat
+ *
+ * This file is part of TwitchTube.
+ *
+ * TwitchTube is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * TwitchTube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TwitchTube. If not, see .
+ */
+
+import QtQuick 2.1
+import Sailfish.Silica 1.0
+
+CoverBackground {
+ property string status: pageStack.currentPage.navStatus
+
+ CoverPlaceholder {
+ icon.source: "../images/icon.png"
+ text: status
+ }
+}
diff --git a/ubuntu-ui/cover/StreamCover.qml b/ubuntu-ui/cover/StreamCover.qml
new file mode 100755
index 0000000..fe75a56
--- /dev/null
+++ b/ubuntu-ui/cover/StreamCover.qml
@@ -0,0 +1,143 @@
+/*
+ * Copyright © 2015-2016 Andrew Penkrat
+ *
+ * This file is part of TwitchTube.
+ *
+ * TwitchTube is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * TwitchTube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TwitchTube. If not, see .
+ */
+
+import QtQuick 2.1
+import Sailfish.Silica 1.0
+import QtMultimedia 5.0
+import "../js/httphelper.js" as HTTP
+
+// The most of this code was taken from Sailfish Silica components
+
+CoverBackground {
+ id: root
+
+ property string channel: mainWindow.currentChannel
+
+ onChannelChanged: {
+ HTTP.getRequest("https://api.twitch.tv/kraken/streams/" + channel, function(data) {
+ if(data) {
+ var stream = JSON.parse(data).stream
+ streamPreview.source = stream.preview.template.replace("{height}", root.height).replace("{width}", ~~(root.height*16/9))
+ statusContainer.text = stream.channel.status
+ }
+ })
+ }
+
+ CoverActionList {
+ CoverAction {
+ iconSource: mainWindow.playing ? "image://theme/icon-m-speaker-mute" : "image://theme/icon-m-speaker"
+ onTriggered: {
+ if(mainWindow.playing)
+ mainWindow.stopAudio()
+ else
+ mainWindow.playAudio()
+ }
+ }
+ }
+
+ Item {
+ id: glassTextureItem
+ visible: false
+ width: glassTextureImage.width
+ height: glassTextureImage.height
+ Image {
+ id: glassTextureImage
+ opacity: 0.1
+ scale: Theme.pixelRatio
+ source: "image://theme/graphic-shader-texture"
+ Behavior on opacity { FadeAnimation { duration: 200 } }
+ }
+ }
+
+ Image {
+ id: streamPreview
+ anchors.fill: parent
+ visible: false
+ fillMode: Image.PreserveAspectCrop
+
+ onSourceChanged: {
+ // Workaround -- seems to be necessary for the ShaderEffect to update the texture
+ wallpaperEffect.wallpaperTexture = null
+ wallpaperEffect.wallpaperTexture = streamPreview
+ }
+ }
+
+ ShaderEffect {
+ id: wallpaperEffect
+ anchors.fill: parent
+
+ property real horizontalOffset: -(root.height*16/9) / 2 + root.width / 2
+ property real verticalOffset: 0
+
+ visible: streamPreview.source != ""
+
+ // offset normalized to effect size
+ property size offset: Qt.size(horizontalOffset / width, verticalOffset / height)
+
+ // ratio of effect size vs home wallpaper size
+ property real ratio: 1
+ property size sizeRatio: Qt.size((9/16)*width/height, 1)
+
+ // glass texture size
+ property size glassTextureSizeInv: Qt.size(1.0/glassTextureImage.sourceSize.width, -1.0/glassTextureImage.sourceSize.height)
+
+ property Image wallpaperTexture: streamPreview
+ property variant glassTexture: ShaderEffectSource {
+ hideSource: true
+ sourceItem: glassTextureItem
+ wrapMode: ShaderEffectSource.Repeat
+ }
+
+ opacity: 0.8
+
+ // Enable blending in compositor (for events view etc..)
+ blending: true
+
+ vertexShader: "
+ uniform highp mat4 qt_Matrix;
+ uniform highp vec2 offset;
+ uniform highp vec2 sizeRatio;
+ attribute highp vec4 qt_Vertex;
+ attribute highp vec2 qt_MultiTexCoord0;
+ varying highp vec2 qt_TexCoord0;
+ void main() {
+ qt_TexCoord0 = (qt_MultiTexCoord0 - offset) * sizeRatio;
+ gl_Position = qt_Matrix * qt_Vertex;
+ }
+ "
+
+ fragmentShader: "
+ uniform sampler2D wallpaperTexture;
+ uniform sampler2D glassTexture;
+ uniform highp vec2 glassTextureSizeInv;
+ uniform lowp float qt_Opacity;
+ varying highp vec2 qt_TexCoord0;
+ void main() {
+ lowp vec4 wp = texture2D(wallpaperTexture, qt_TexCoord0);
+ lowp vec4 tx = texture2D(glassTexture, gl_FragCoord.xy * glassTextureSizeInv);
+ gl_FragColor = vec4(0.8*wp.rgb + tx.rgb, 1.0)" + (blending ? "*qt_Opacity" : "") + ";
+ }
+ "
+ }
+
+ CoverPlaceholder {
+ id: statusContainer
+ icon.source: "../images/icon.png"
+ }
+}
diff --git a/ubuntu-ui/images/heart.png b/ubuntu-ui/images/heart.png
new file mode 100755
index 0000000..81ef011
Binary files /dev/null and b/ubuntu-ui/images/heart.png differ
diff --git a/ubuntu-ui/images/heart_crossed.png b/ubuntu-ui/images/heart_crossed.png
new file mode 100755
index 0000000..d34a331
Binary files /dev/null and b/ubuntu-ui/images/heart_crossed.png differ
diff --git a/ubuntu-ui/images/icon.png b/ubuntu-ui/images/icon.png
new file mode 100755
index 0000000..b5e6f54
Binary files /dev/null and b/ubuntu-ui/images/icon.png differ
diff --git a/ubuntu-ui/js/httphelper.js b/ubuntu-ui/js/httphelper.js
new file mode 100644
index 0000000..1dc375d
--- /dev/null
+++ b/ubuntu-ui/js/httphelper.js
@@ -0,0 +1,95 @@
+/*
+ * Copyright © 2015-2016 Andrew Penkrat
+ *
+ * This file is part of TwitchTube.
+ *
+ * TwitchTube is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * TwitchTube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TwitchTube. If not, see .
+ */
+.pragma library
+
+function getRequest(url, callback, publicAPI) {
+ var request = new XMLHttpRequest()
+ request.open("GET", url)
+ if(url.indexOf("https://api.twitch.tv/kraken") === 0) {
+ // Kraken API
+ request.setRequestHeader("Accept", "application/vnd.twitchtv.v3+json")
+ request.setRequestHeader("Client-ID", "n57dx0ypqy48ogn1ac08buvoe13bnsu")
+ } else if(publicAPI) {
+ // Experimental API
+ request.setRequestHeader("Accept", "application/vnd.twitchtv.v3+json")
+ request.setRequestHeader("Client-ID", "n57dx0ypqy48ogn1ac08buvoe13bnsu")
+ }
+ request.onreadystatechange = function() {
+ if (request.readyState === XMLHttpRequest.DONE) {
+ if (request.status && request.status === 200) {
+ callback(request.responseText)
+ } else {
+ console.log("Error accessing url", url)
+ console.log("HTTP:", request.status, request.statusText)
+ callback(false)
+ }
+ }
+ }
+ request.send()
+}
+
+function putRequest(url, callback, publicAPI) {
+ var request = new XMLHttpRequest()
+ request.open("PUT", url)
+ if(url.indexOf("https://api.twitch.tv/kraken") === 0) {
+ // Kraken API
+ request.setRequestHeader("Accept", "application/vnd.twitchtv.v3+json")
+ request.setRequestHeader("Client-ID", "n57dx0ypqy48ogn1ac08buvoe13bnsu")
+ } else if(publicAPI) {
+ // Experimental API
+ request.setRequestHeader("Accept", "application/vnd.twitchtv.v3+json")
+ request.setRequestHeader("Client-ID", "n57dx0ypqy48ogn1ac08buvoe13bnsu")
+ }
+ request.onreadystatechange = function() {
+ if (request.readyState === XMLHttpRequest.DONE) {
+ if (request.status && request.status === 200) {
+ callback(request.responseText)
+ } else {
+ console.log("HTTP:", request.status, request.statusText)
+ callback(false)
+ }
+ }
+ }
+ request.send()
+}
+
+function deleteRequest(url, callback, publicAPI) {
+ var request = new XMLHttpRequest()
+ request.open("DELETE", url)
+ if(url.indexOf("https://api.twitch.tv/kraken") === 0) {
+ // Kraken API
+ request.setRequestHeader("Accept", "application/vnd.twitchtv.v3+json")
+ request.setRequestHeader("Client-ID", "n57dx0ypqy48ogn1ac08buvoe13bnsu")
+ } else if(publicAPI) {
+ // Experimental API
+ request.setRequestHeader("Accept", "application/vnd.twitchtv.v3+json")
+ request.setRequestHeader("Client-ID", "n57dx0ypqy48ogn1ac08buvoe13bnsu")
+ }
+ request.onreadystatechange = function() {
+ if (request.readyState === XMLHttpRequest.DONE) {
+ if (request.status && request.status === 200) {
+ callback(request.responseText)
+ } else {
+ console.log("HTTP:", request.status, request.statusText)
+ callback(request.status)
+ }
+ }
+ }
+ request.send()
+}
diff --git a/ubuntu-ui/pages/ChannelsPage.qml b/ubuntu-ui/pages/ChannelsPage.qml
new file mode 100755
index 0000000..e45b8e2
--- /dev/null
+++ b/ubuntu-ui/pages/ChannelsPage.qml
@@ -0,0 +1,62 @@
+/*
+ * Copyright © 2015-2016 Andrew Penkrat
+ *
+ * This file is part of TwitchTube.
+ *
+ * TwitchTube is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * TwitchTube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TwitchTube. If not, see .
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+import "elements"
+import "../js/httphelper.js" as HTTP
+
+Page {
+ id: page
+
+ // Status for NavigationCover
+ property string navStatus: qsTr("Channels")
+
+ header: PageHeader {
+ title: qsTr("Top Channels")
+ flickable: mainContainer
+
+ leadingActionBar.actions: categories.actions
+ Categories {
+ id: categories
+ channels: false
+ }
+ }
+
+ GridWrapper {
+ id: mainContainer
+ grids: [
+ ChannelsGrid {
+ id: gridChannels
+
+ function loadContent() {
+ var url = "https://api.twitch.tv/kraken/streams?limit=" + countOnPage + "&offset=" + offset
+ HTTP.getRequest(url,function(data) {
+ if (data) {
+ offset += countOnPage
+ var result = JSON.parse(data)
+ totalCount = result._total
+ for (var i in result.streams)
+ channels.append(result.streams[i])
+ }
+ })
+ }
+ }]
+ }
+}
diff --git a/ubuntu-ui/pages/FollowedGamesPage.qml b/ubuntu-ui/pages/FollowedGamesPage.qml
new file mode 100755
index 0000000..c99e97f
--- /dev/null
+++ b/ubuntu-ui/pages/FollowedGamesPage.qml
@@ -0,0 +1,60 @@
+/*
+ * Copyright © 2015-2016 Andrew Penkrat
+ *
+ * This file is part of TwitchTube.
+ *
+ * TwitchTube is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * TwitchTube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TwitchTube. If not, see .
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+import "elements"
+import "../js/httphelper.js" as HTTP
+
+Page {
+ id: page
+
+ // Status for NavigationCover
+ property string navStatus: qsTr("Following")
+
+ header: PageHeader {
+ title: qsTr("Followed Games")
+ flickable: mainContainer
+ }
+
+ GridWrapper {
+ id: mainContainer
+
+ grids: [
+ GamesGrid {
+ id: gridGames
+
+ function loadContent() {
+ var url = "https://api.twitch.tv/api/users/" + mainWindow.username + "/follows/games?limit=" + countOnPage + "&offset=" + offset
+ console.log(url)
+ HTTP.getRequest(url,function(data) {
+ if (data) {
+ offset += countOnPage
+ var result = JSON.parse(data)
+ totalCount = result._total
+ for (var i in result.follows)
+ games.append(result.follows[i])
+ }
+ })
+ }
+
+ parameters: { "fromFollowings": true }
+ }]
+ }
+}
diff --git a/ubuntu-ui/pages/FollowedPage.qml b/ubuntu-ui/pages/FollowedPage.qml
new file mode 100755
index 0000000..2725461
--- /dev/null
+++ b/ubuntu-ui/pages/FollowedPage.qml
@@ -0,0 +1,77 @@
+/*
+ * Copyright © 2015-2016 Andrew Penkrat
+ *
+ * This file is part of TwitchTube.
+ *
+ * TwitchTube is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * TwitchTube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TwitchTube. If not, see .
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+import "elements"
+import "../js/httphelper.js" as HTTP
+
+Page {
+ id: page
+
+ // Status for NavigationCover
+ property string navStatus: qsTr("Following")
+ property bool showOffline: false
+
+ header: PageHeader {
+ title: qsTr("Followed Channels")
+ flickable: mainContainer
+
+ leadingActionBar.actions: categories.actions
+ Categories {
+ id: categories
+ games: false
+ }
+
+ trailingActionBar.actions: [
+ Action {
+ iconName: "view-expand"
+ onTriggered: pageStack.push(Qt.resolvedUrl("FollowedGamesPage.qml"))
+ }
+ ]
+ }
+
+ GridWrapper {
+ id: mainContainer
+ grids: [
+ ChannelsGrid {
+ id: gridChannels
+
+ function loadContent() {
+ var url = "https://api.twitch.tv/kraken/streams/followed?limit=" + countOnPage + "&offset=" + offset + "&oauth_token=" + authToken.value
+ console.log(url)
+ HTTP.getRequest(url, function(data) {
+ if (data) {
+ offset += countOnPage
+ var result = JSON.parse(data)
+ totalCount = result._total
+ for (var i in result.streams)
+ channels.append(result.streams[i])
+ }
+ })
+ }
+
+ onLoadMoreAvailableChanged: {
+ if(!loadMoreAvailable && !showOffline) {
+ showOffline = true
+ }
+ }
+ }]
+ }
+}
diff --git a/ubuntu-ui/pages/GameChannelsPage.qml b/ubuntu-ui/pages/GameChannelsPage.qml
new file mode 100755
index 0000000..c3b49cd
--- /dev/null
+++ b/ubuntu-ui/pages/GameChannelsPage.qml
@@ -0,0 +1,96 @@
+/*
+ * Copyright © 2015-2016 Andrew Penkrat
+ *
+ * This file is part of TwitchTube.
+ *
+ * TwitchTube is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * TwitchTube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TwitchTube. If not, see .
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+import QtGraphicalEffects 1.0
+import "elements"
+import "../js/httphelper.js" as HTTP
+
+Page {
+ id: page
+
+ property string game
+ property bool followed
+ property bool fromFollowings: false
+ // Status for NavigationCover
+ property string navStatus: game
+
+ function checkIfFollowed() {
+ followed = false
+ if(mainWindow.username) {
+ HTTP.getRequest("https://api.twitch.tv/api/users/" + mainWindow.username + "/follows/games/" + game, function(data) {
+ if(data)
+ followed = true
+ else
+ followed = false
+ })
+ }
+ }
+
+ header: PageHeader {
+ title: qsTr("Top Games")
+ flickable: mainContainer
+
+ trailingActionBar.actions: [
+ Action {
+ enabled: mainWindow.username
+ iconName: followed ? "starred" : "non-starred"
+ name: followed ? qsTr("Unfollow") : qsTr("Follow")
+
+ Component.onCompleted: checkIfFollowed()
+ onEnabledChanged: checkIfFollowed()
+ onTriggered: {
+ if(!followed)
+ HTTP.putRequest("https://api.twitch.tv/api/users/" + username + "/follows/games/" + game + "?oauth_token=" + authToken.value, function(data) {
+ if(data)
+ followed = true
+ })
+ else
+ HTTP.deleteRequest("https://api.twitch.tv/api/users/" + username + "/follows/games/" + game + "?oauth_token=" + authToken.value, function(data) {
+ if(data === 204)
+ followed = false
+ })
+ }
+ }
+ ]
+ }
+
+ GridWrapper {
+ id: mainContainer
+
+ grids: [
+ ChannelsGrid {
+ id: gridChannels
+
+ function loadContent() {
+ var url = "https://api.twitch.tv/kraken/streams?limit=" + countOnPage + "&offset=" + offset + encodeURI("&game=" + game)
+ HTTP.getRequest(url,function(data) {
+ if (data) {
+ offset += countOnPage
+ var result = JSON.parse(data)
+ totalCount = result._total
+ for (var i in result.streams)
+ channels.append(result.streams[i])
+ }
+ })
+ }
+ }]
+ }
+}
diff --git a/ubuntu-ui/pages/GamesPage.qml b/ubuntu-ui/pages/GamesPage.qml
new file mode 100755
index 0000000..f9ee871
--- /dev/null
+++ b/ubuntu-ui/pages/GamesPage.qml
@@ -0,0 +1,64 @@
+/*
+ * Copyright © 2015-2016 Andrew Penkrat
+ *
+ * This file is part of TwitchTube.
+ *
+ * TwitchTube is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * TwitchTube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TwitchTube. If not, see .
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+import "elements"
+import "../js/httphelper.js" as HTTP
+
+Page {
+ id: page
+
+ // Status for NavigationCover
+ property string navStatus: qsTr("Games")
+
+ header: PageHeader {
+ title: qsTr("Top Games")
+ flickable: mainContainer
+
+ leadingActionBar.actions: categories.actions
+ Categories {
+ id: categories
+ games: false
+ }
+ }
+
+ GridWrapper {
+ id: mainContainer
+
+ grids: [
+ GamesGrid {
+ id: gridGames
+
+ function loadContent() {
+ var url = "https://api.twitch.tv/kraken/games/top?limit=" + countOnPage + "&offset=" + offset
+ console.log(url)
+ HTTP.getRequest(url,function(data) {
+ if (data) {
+ offset += countOnPage
+ var result = JSON.parse(data)
+ totalCount = result._total
+ for (var i in result.top)
+ games.append(result.top[i].game)
+ }
+ })
+ }
+ }]
+ }
+}
diff --git a/ubuntu-ui/pages/LoginPage.qml b/ubuntu-ui/pages/LoginPage.qml
new file mode 100755
index 0000000..3264692
--- /dev/null
+++ b/ubuntu-ui/pages/LoginPage.qml
@@ -0,0 +1,80 @@
+/*
+ * Copyright © 2015-2016 Andrew Penkrat
+ *
+ * This file is part of TwitchTube.
+ *
+ * TwitchTube is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * TwitchTube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TwitchTube. If not, see .
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+import Ubuntu.Web 0.2
+
+Page {
+ id: page
+
+ property bool needExit: false
+ // Status for NavigationCover
+ property string navStatus: qsTr("Settings")
+
+ header: PageHeader {
+ id: head
+ title: qsTr("Log into Twitch account")
+ }
+
+ WebView {
+ id: twitchLogin
+
+ anchors {
+ top: head.bottom
+ bottom: parent.bottom
+ left: parent.left
+ right: parent.right
+ }
+
+ onUrlChanged: {
+ var newurl = url.toString()
+ if(newurl.indexOf("http://localhost") === 0) {
+ var params = newurl.substring(newurl.lastIndexOf('/') + 1)
+ if(params.indexOf("#access_token") >= 0) {
+ authToken.value = params.split('=')[1].split('&')[0]
+ mainWindow.userChanged()
+ }
+ pageStack.pop()
+ }
+ }
+
+ onNavigationRequested: {
+ var rurl = request.url.toString()
+ console.log(request)
+ console.log(rurl)
+ console.log(url)
+ if(rurl.indexOf("http://localhost") === 0) {
+ var params = rurl.substring(rurl.lastIndexOf('/') + 1)
+ if(params.indexOf("#access_token") >= 0) {
+ authToken.value = params.split('=')[1].split('&')[0]
+ }
+ if(status === PageStatus.Activating)
+ needExit = true
+ else
+ pageStack.pop()
+ }
+ else
+ request.action = WebView.AcceptRequest;
+ }
+ url: encodeURI("https://api.twitch.tv/kraken/oauth2/authorize?response_type=token&client_id=n57dx0ypqy48ogn1ac08buvoe13bnsu&redirect_uri=http://localhost&scope=user_read user_follows_edit chat_login")
+ }
+
+ //onStatusChanged: if(status === PageStatus.Active && needExit) pageStack.pop()
+}
diff --git a/ubuntu-ui/pages/QualityChooserPage.qml b/ubuntu-ui/pages/QualityChooserPage.qml
new file mode 100755
index 0000000..8458aa2
--- /dev/null
+++ b/ubuntu-ui/pages/QualityChooserPage.qml
@@ -0,0 +1,160 @@
+/*
+ * Copyright © 2015-2016 Andrew Penkrat
+ *
+ * This file is part of TwitchTube.
+ *
+ * TwitchTube is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * TwitchTube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TwitchTube. If not, see .
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+
+Page {
+ id: page
+
+ property var qualities: ["chunked", "high", "medium", "low", "mobile"]
+ property bool chatOnly
+ property bool audioOnly
+
+ signal accepted
+
+ header: PageHeader {
+ id: header
+
+ title: qsTr("Stream properties")
+ flickable: mainContainer
+ trailingActionBar.actions: [
+ Action {
+ text: qsTr("Apply")
+ iconName: "ok"
+
+ onTriggered: {
+ streamQuality.value = qualities[qualityChooser.currentIndex]
+ chatOnly = chatOnlySwitch.checked
+ audioOnly = audioOnlySwitch.checked
+ accepted()
+ console.log("accepted")
+ pageStack.pop()
+ }
+ },
+
+ Action {
+ text: qsTr("Cancel")
+ iconName: "close"
+
+ onTriggered: pageStack.pop()
+ }
+ ]
+ }
+
+ Flickable {
+ id: mainContainer
+ anchors.fill: parent
+ contentHeight: header.height + optionsContainer.height
+
+ Column {
+ id: optionsContainer
+
+ anchors {
+ top: header.bottom
+ left: parent.left
+ right: parent.right
+ }
+
+ ListItem {
+ width: parent.width
+ height: qualityChooser.height + units.gu(4)
+
+ OptionSelector {
+ id: qualityChooser
+
+ anchors {
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ margins: units.gu(2)
+ }
+
+ text: qsTr("Quality")
+ model: [
+ qsTr("Source"),
+ qsTr("High"),
+ qsTr("Medium"),
+ qsTr("Low"),
+ qsTr("Mobile")
+ ]
+ selectedIndex: qualities.indexOf(streamQuality.value)
+ }
+ }
+
+ ListItem {
+ width: parent.width
+
+ Label {
+ anchors {
+ left: parent.left
+ top: parent.top
+ margins: units.gu(2)
+ }
+
+ text: qsTr("Chat only")
+ }
+
+ Switch {
+ id: chatOnlySwitch
+ anchors {
+ right: parent.right
+ top: parent.top
+ margins: units.gu(2)
+ }
+ checked: chatOnly
+
+ onCheckedChanged: {
+ if(checked)
+ audioOnlySwitch.checked = false
+ }
+ }
+ }
+
+ ListItem {
+ width: parent.width
+
+ Label {
+ anchors {
+ left: parent.left
+ top: parent.top
+ margins: units.gu(2)
+ }
+
+ text: qsTr("Audio only")
+ }
+
+ Switch {
+ id: audioOnlySwitch
+ anchors {
+ right: parent.right
+ top: parent.top
+ margins: units.gu(2)
+ }
+ checked: audioOnly
+
+ onCheckedChanged: {
+ if(checked)
+ chatOnlySwitch.checked = false
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/ubuntu-ui/pages/SearchPage.qml b/ubuntu-ui/pages/SearchPage.qml
new file mode 100755
index 0000000..7f58e45
--- /dev/null
+++ b/ubuntu-ui/pages/SearchPage.qml
@@ -0,0 +1,90 @@
+/*
+ * Copyright © 2015-2016 Andrew Penkrat
+ *
+ * This file is part of TwitchTube.
+ *
+ * TwitchTube is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * TwitchTube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TwitchTube. If not, see .
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+import "elements"
+import "../js/httphelper.js" as HTTP
+
+Page {
+ id: page
+
+ // Status for NavigationCover
+ property string navStatus: qsTr("Search")
+
+ property string querry: ""
+
+ header: PageHeader {
+
+ flickable: mainContainer
+
+ contents: TextField {
+ id: searchQuerry
+
+ anchors.fill: parent
+ anchors.margins: units.gu(1)
+
+ hasClearButton: true
+ placeholderText: qsTr("Search channels")
+ onTextChanged: {
+ gridResults.channels.clear()
+ gridResults.offset = 0
+ page.querry = text
+ gridResults.loadContent()
+ }
+ }
+
+ leadingActionBar.actions: categories.actions
+ Categories {
+ id: categories
+ search: false
+ }
+ }
+
+ GridWrapper {
+ id: mainContainer
+ grids: [
+ ChannelsGrid {
+ id: gridResults
+
+ function loadContent() {
+ if(querry) {
+ var url = "https://api.twitch.tv/kraken/search/streams?q=" + querry + "&limit=" + countOnPage + "&offset=" + offset
+ console.log(url)
+ HTTP.getRequest(url,function(data) {
+ if (data) {
+ offset += countOnPage
+ var result = JSON.parse(data)
+ totalCount = result._total
+ for (var i in result.streams)
+ channels.append(result.streams[i])
+ }
+ })
+ }
+ else {
+ totalCount = 0
+ }
+ }
+ autoLoad: false
+
+ // This prevents search field from loosing focus when grid changes
+ currentIndex: -1
+ }]
+ }
+}
diff --git a/ubuntu-ui/pages/SettingsPage.qml b/ubuntu-ui/pages/SettingsPage.qml
new file mode 100755
index 0000000..5f05816
--- /dev/null
+++ b/ubuntu-ui/pages/SettingsPage.qml
@@ -0,0 +1,246 @@
+/*
+ * Copyright © 2015-2016 Andrew Penkrat
+ *
+ * This file is part of TwitchTube.
+ *
+ * TwitchTube is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * TwitchTube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TwitchTube. If not, see .
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+import "../js/httphelper.js" as HTTP
+
+Page {
+ id: page
+
+ property var imageSizes: ["large", "medium", "small"]
+ property string name
+ // Status for NavigationCover
+ property string navStatus: qsTr("Settings")
+
+ function getName() {
+ if(authToken.value) {
+ HTTP.getRequest("https://api.twitch.tv/kraken/user?oauth_token=" + authToken.value, function(data) {
+ var user = JSON.parse(data)
+ name = user.display_name
+ mainWindow.username = user.name
+ })
+ } else {
+ name = ""
+ mainWindow.username = ""
+ }
+ }
+
+ Component.onCompleted: getName()
+ Connections {
+ target: mainWindow
+ onUserChanged: getName()
+ }
+
+ header: PageHeader {
+ id: header
+
+ title: qsTr("Settings")
+ flickable: mainContainer
+ trailingActionBar.actions: [
+ Action {
+ text: qsTr("Apply")
+ iconName: "ok"
+
+ onTriggered: {
+ gameImageSize.value = imageSizes[gameQ.selectedIndex]
+ channelImageSize.value = imageSizes[previewQ.selectedIndex]
+ showBroadcastTitles.value = streamTitles.checked
+ chatFlowBtT.value = chatTtB.checked
+ pageStack.pop()
+ }
+ },
+ Action {
+ text: qsTr("Cancel")
+ iconName: "close"
+
+ onTriggered: pageStack.pop()
+ }
+ ]
+ }
+
+ Flickable {
+ id: mainContainer
+ anchors.fill: parent
+ contentHeight: header.height + settingsContainer.height + units.gu(2) // for bottom margin
+
+ Column {
+ id: settingsContainer
+
+ anchors {
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ }
+
+ ListItem {
+ id: login
+
+ width: parent.width
+ height: lblAcc1.height + lblAcc2.height + 2*units.gu(2) + units.gu(1)
+
+ trailingActions: ListItemActions {
+ actions: [
+ Action {
+ id: accountAction
+ text: !authToken.value ? qsTr("Log in") : qsTr("Log out")
+ iconName: "go-next"
+
+ onTriggered: {
+ console.log("old token:", authToken.value)
+ if(!authToken.value) {
+ var lpage = pageStack.push(Qt.resolvedUrl("LoginPage.qml"))
+ }
+ else {
+ authToken.value = ""
+ console.log("Cookie cleaning script result code:", cpptools.clearCookies())
+ mainWindow.userChanged()
+ }
+ }
+ }
+ ]
+ }
+
+ onClicked: accountAction.trigger()
+
+ Label {
+ id: lblAcc1
+
+ anchors { top: parent.top
+ left: parent.left
+ right: parent.right
+ topMargin: units.gu(2)
+ leftMargin: units.gu(2)
+ rightMargin: units.gu(2)
+ }
+ text: !authToken.value ? qsTr("Not logged in") : (qsTr("Logged in as ") + name)
+ font.pixelSize: FontUtils.sizeToPixels("medium")
+ }
+
+ Label {
+ id: lblAcc2
+
+ anchors { bottom: parent.bottom
+ left: parent.left
+ right: parent.right
+ bottomMargin: units.gu(2)
+ leftMargin: units.gu(2)
+ rightMargin: units.gu(2)
+ }
+ text: !authToken.value ? qsTr("Log in") : qsTr("Log out")
+ color: UbuntuColors.coolGrey
+ font.pixelSize: FontUtils.sizeToPixels("small")
+ }
+ }
+
+ ListItem {
+ width: parent.width
+
+ Label {
+ anchors {
+ left: parent.left
+ top: parent.top
+ margins: units.gu(2)
+ }
+
+ text: qsTr("Show broadcast titles") }
+ Switch {
+ id: streamTitles
+ anchors {
+ right: parent.right
+ top: parent.top
+ margins: units.gu(2)
+ }
+ checked: showBroadcastTitles.value
+ }
+ }
+
+ ListItem {
+ width: parent.width
+
+ Label {
+ anchors {
+ left: parent.left
+ top: parent.top
+ margins: units.gu(2)
+ }
+
+ text: qsTr("Chat flows from bottom to top") }
+
+ Switch {
+ id: chatTtB
+ anchors {
+ right: parent.right
+ top: parent.top
+ margins: units.gu(2)
+ }
+ checked: chatFlowBtT.value
+ }
+ }
+
+ ListItem {
+ width: parent.width
+ height: gameQ.height + units.gu(4)
+
+ OptionSelector {
+ id: gameQ
+
+ anchors {
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ margins: units.gu(2)
+ }
+
+ text: qsTr("Game posters quality")
+ model: [
+ qsTr("High"),
+ qsTr("Medium"),
+ qsTr("Low")
+ ]
+ selectedIndex: imageSizes.indexOf(gameImageSize.value)
+ }
+ }
+
+ ListItem {
+ width: parent.width
+ height: previewQ.height + units.gu(4)
+
+ OptionSelector {
+ id: previewQ
+
+ anchors {
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ margins: units.gu(2)
+ }
+
+ text: qsTr("Stream previews quality")
+ model: [
+ qsTr("High"),
+ qsTr("Medium"),
+ qsTr("Low")
+ ]
+ selectedIndex: imageSizes.indexOf(channelImageSize.value)
+ }
+ }
+ }
+ }
+}
diff --git a/ubuntu-ui/pages/StreamPage.qml b/ubuntu-ui/pages/StreamPage.qml
new file mode 100755
index 0000000..059a2b5
--- /dev/null
+++ b/ubuntu-ui/pages/StreamPage.qml
@@ -0,0 +1,368 @@
+/*
+ * Copyright © 2015-2016 Andrew Penkrat
+ *
+ * This file is part of TwitchTube.
+ *
+ * TwitchTube is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * TwitchTube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TwitchTube. If not, see .
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+import QtMultimedia 5.0
+import aldrog.twitchtube.ircchat 1.0
+import "../js/httphelper.js" as HTTP
+
+Page {
+ id: page
+
+ property var url
+ property string channel
+ property string username
+ property bool followed
+ property bool chatMode: false
+ property bool audioMode: false
+ property bool active: Qt.application.active
+ property bool isLandscape: width > height
+ property bool isPortrait: !isLandscape
+ property bool fullscreenConditions: isLandscape && main.visibleArea.yPosition === 0 && !main.moving && !state && video.visible
+
+ function findUrl(s, q) {
+ for (var x in s) {
+ if (s[x].substring(0,4) === "http" && s[x].indexOf(q) >= 0)
+ return s[x]
+ }
+ }
+
+ function loadStreamInfo() {
+ HTTP.getRequest("http://api.twitch.tv/api/channels/" + channel + "/access_token", function (tokendata) {
+ if (tokendata) {
+ var token = JSON.parse(tokendata)
+ HTTP.getRequest(encodeURI("http://usher.twitch.tv/api/channel/hls/" + channel + ".json?allow_source=true&allow_audio_only=true&sig=" + token.sig + "&token=" + token.token + "&type=any"), function (data) {
+ if (data) {
+ var videourls = data.split('\n')
+ url = {
+ chunked: findUrl(videourls, "chunked"),
+ high: findUrl(videourls, "high"),
+ medium: findUrl(videourls, "medium"),
+ low: findUrl(videourls, "low"),
+ mobile: findUrl(videourls, "mobile"),
+ audio: findUrl(videourls, "audio_only")
+ }
+ video.play()
+ mainWindow.audioUrl = url.audio
+ }
+ })
+ }
+ })
+ }
+
+ function checkFollow() {
+ if(mainWindow.username) {
+ HTTP.getRequest("https://api.twitch.tv/kraken/users/" + mainWindow.username + "/follows/channels/" + channel, function(data) {
+ if(data)
+ return true
+ })
+ }
+ return false
+ }
+
+ onChatModeChanged: {
+ if(chatMode)
+ video.stop()
+ }
+
+// onStatusChanged: {
+// if(status === PageStatus.Activating) {
+// mainWindow.currentChannel = channel
+// mainWindow.cover = Qt.resolvedUrl("../cover/StreamCover.qml")
+// cpptools.setBlankingMode(false)
+// }
+// if(status === PageStatus.Deactivating) {
+// if (_navigation === PageNavigation.Back) {
+// mainWindow.cover = Qt.resolvedUrl("../cover/NavigationCover.qml")
+// }
+// cpptools.setBlankingMode(true)
+// }
+// }
+
+// onActiveChanged: {
+// if(page.status === PageStatus.Active) {
+// if(active) {
+// mainWindow.stopAudio()
+// video.play()
+// if(!twitchChat.connected) {
+// twitchChat.reopenSocket()
+// twitchChat.join(channel)
+// }
+// }
+// else {
+// video.pause()
+// if(audioMode)
+// mainWindow.playAudio()
+// if(twitchChat.connected)
+// twitchChat.disconnect()
+// }
+// }
+// }
+
+ Component.onCompleted: {
+ loadStreamInfo()
+ followed = checkFollow()
+ }
+
+ header: PageHeader {
+ title: channel
+ flickable: main
+
+ trailingActionBar.actions: [
+ Action {
+ enabled: mainWindow.username
+ iconName: followed ? "starred" : "non-starred"
+ name: followed ? qsTr("Unfollow") : qsTr("Follow")
+
+ onTriggered: {
+ if(!followed) {
+ HTTP.putRequest("https://api.twitch.tv/kraken/users/" + username + "/follows/channels/" + channel + "?oauth_token=" + authToken.value, function(data) {
+ if(data)
+ followed = true
+ })
+ } else {
+ HTTP.deleteRequest("https://api.twitch.tv/kraken/users/" + username + "/follows/channels/" + channel + "?oauth_token=" + authToken.value, function(data) {
+ if(data === 204)
+ followed = false
+ })
+ }
+ }
+ },
+
+ Action {
+ iconName: "settings"
+ name: qsTr("Quality")
+ onTriggered: {
+ pageStack.push(streamSettings, { chatOnly: chatMode, audioOnly: audioMode, channel: channel })
+ }
+ }
+ ]
+ }
+
+ QualityChooserPage {
+ id: streamSettings
+ onAccepted: {
+ chatMode = chatOnly
+ audioMode = audioOnly
+ console.log("Chat mode", chatMode)
+ console.log("Audio mode", audioMode)
+ }
+ }
+
+ Timer {
+ id: fullscreenTimer
+
+ interval: 3000
+ running: fullscreenConditions
+ onTriggered: page.state = "fullscreen"
+ }
+
+ Flickable {
+ id: main
+
+ anchors.fill: parent
+ contentHeight: isPortrait ? page.height : (chatMode ? page.height : (5/3 * page.height))
+ //onContentHeightChanged: console.log(contentHeight, height + Screen.width, Screen.width, chat.height)
+
+// PullDownMenu {
+// id: streamMenu
+
+// MenuItem {
+// text: qsTr("Follow")
+// onClicked: HTTP.putRequest("https://api.twitch.tv/kraken/users/" + username + "/follows/channels/" + channel + "?oauth_token=" + authToken.value, function(data) {
+// if(data)
+// followed = true
+// })
+// visible: mainWindow.username && !followed
+// }
+
+// MenuItem {
+// text: qsTr("Unfollow")
+// onClicked: HTTP.deleteRequest("https://api.twitch.tv/kraken/users/" + username + "/follows/channels/" + channel + "?oauth_token=" + authToken.value, function(data) {
+// if(data === 204)
+// followed = false
+// })
+// visible: mainWindow.username && followed
+// }
+
+// MenuItem {
+// text: qsTr("Quality")
+// onClicked: {
+// var dialog = pageStack.push(Qt.resolvedUrl("QualityChooserPage.qml"), { chatOnly: chatMode, audioOnly: audioMode, channel: channel })
+// dialog.accepted.connect(function() {
+// chatMode = dialog.chatOnly
+// audioMode = dialog.audioOnly
+// })
+// }
+// }
+// }
+
+ Rectangle {
+ id: videoBackground
+
+ color: "black"
+ anchors.top: parent.top; anchors.left: parent.left; anchors.right: parent.right
+ height: (!chatMode && !audioMode) ? (page.width * 9/16) : 0
+ visible: (!chatMode && !audioMode)
+
+ Video {
+ id: video
+
+ anchors.fill: parent
+ source: audioMode ? url["audio"] : url[streamQuality.value]
+
+ onErrorChanged: console.error("video error:", errorString)
+
+ ActivityIndicator {
+ anchors.centerIn: parent
+ running: video.playbackState !== MediaPlayer.PlayingState
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ page.state = !page.state ? "fullscreen" : ""
+ console.log(page.state)
+ }
+ }
+ }
+ }
+
+ TextField {
+ id: chatMessage
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ top: chatFlowBtT.value ? videoBackground.bottom : undefined
+ bottom: chatFlowBtT.value ? undefined : parent.bottom
+ topMargin: chatMode ? Theme.paddingLarge : Theme.paddingMedium
+ bottomMargin: Theme.paddingMedium
+ }
+ // Maybe it's better to replace ternary operators with if else blocks
+ placeholderText: twitchChat.connected ? (twitchChat.anonymous ? qsTr("Please log in to send messages") : qsTr("Type your message here")) : qsTr("Chat is not available")
+ enabled: twitchChat.connected && !twitchChat.anonymous
+ inputMask: "X"
+ onAccepted: {
+ twitchChat.sendMessage(text)
+ text = ""
+ }
+ }
+
+ ListView {
+ id: chat
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ top: chatFlowBtT.value ? chatMessage.bottom : videoBackground.bottom
+ bottom: chatFlowBtT.value ? parent.bottom : chatMessage.top
+ //topMargin: (chatMode && !chatFlowBtT.value) ? 0 : Theme.paddingMedium
+ //bottomMargin: 0//chatFlowBtT.value ? Theme.paddingLarge : Theme.paddingMedium
+ }
+
+ highlightRangeMode: count > 0 ? ListView.StrictlyEnforceRange : ListView.NoHighlightRange
+ //preferredHighlightBegin: chat.height - currentItem.height
+ preferredHighlightEnd: chat.height
+ clip: true
+ verticalLayoutDirection: chatFlowBtT.value ? ListView.BottomToTop : ListView.TopToBottom
+
+ model: twitchChat.messages
+ delegate: Item {
+ height: lbl.height
+ width: chat.width
+
+ ListView.onAdd: {
+ if(chat.currentIndex >= chat.count - 3) {
+ chat.currentIndex = chat.count - 1
+ }
+ }
+
+ Label {
+ id: lbl
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ leftMargin: Theme.horizontalPageMargin
+ rightMargin: Theme.horizontalPageMargin
+ }
+
+ text: richTextMessage
+ textFormat: Text.RichText
+ wrapMode: Text.WordWrap
+ color: isNotice ? UbuntuColors.orange : UbuntuColors.ash
+ }
+ }
+
+ IrcChat {
+ id: twitchChat
+
+ name: mainWindow.username
+ password: 'oauth:' + authToken.value
+ anonymous: !mainWindow.username
+ textSize: 14
+
+ Component.onCompleted: {
+ twitchChat.join(channel)
+ }
+
+ onErrorOccured: {
+ console.log("Chat error: ", errorDescription)
+ }
+
+ onConnectedChanged: {
+ console.log(connected)
+ }
+ }
+ }
+ }
+
+ states: State {
+ name: "fullscreen"
+ PropertyChanges {
+ target: main
+ contentHeight: page.height
+ }
+
+ PropertyChanges {
+ target: chatMessage
+ visible: false
+ }
+
+ PropertyChanges {
+ target: chat
+ visible: false
+ }
+
+// PropertyChanges {
+// target: streamMenu
+// visible: false
+// active: false
+// }
+
+ PropertyChanges {
+ target: mainWindow
+ // special flag only supported by Unity8/MIR so far that hides the shell's
+ // top panel in Staged mode
+ flags: Qt.Window | 0x00800000
+ }
+ }
+}
diff --git a/ubuntu-ui/pages/elements/Categories.qml b/ubuntu-ui/pages/elements/Categories.qml
new file mode 100755
index 0000000..a71421d
--- /dev/null
+++ b/ubuntu-ui/pages/elements/Categories.qml
@@ -0,0 +1,73 @@
+/*
+ * Copyright © 2015-2016 Andrew Penkrat
+ *
+ * This file is part of TwitchTube.
+ *
+ * TwitchTube is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * TwitchTube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TwitchTube. If not, see .
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+
+Item {
+ property bool search: true
+ property bool following: true
+ property bool channels: true
+ property bool games: true
+
+ property list actions: [
+ Action {
+ text: qsTr("Games")
+ visible: games
+ onTriggered: {
+ pageStack.pop()
+ pageStack.push(Qt.resolvedUrl("../GamesPage.qml"))
+ }
+ },
+
+ Action {
+ text: qsTr("Channels")
+ visible: channels
+ onTriggered: {
+ pageStack.pop()
+ pageStack.push(Qt.resolvedUrl("../ChannelsPage.qml"))
+ }
+ },
+
+ Action {
+ text: qsTr("Following")
+ visible: following && authToken.value
+ onTriggered: {
+ pageStack.pop()
+ pageStack.push(Qt.resolvedUrl("../FollowedPage.qml"))
+ }
+ },
+
+ Action {
+ text: qsTr("Search")
+ visible: search
+ onTriggered: {
+ pageStack.pop()
+ pageStack.push(Qt.resolvedUrl("../SearchPage.qml"))
+ }
+ },
+
+ Action {
+ text: qsTr("Settings")
+ onTriggered: {
+ pageStack.push(Qt.resolvedUrl("../SettingsPage.qml"))
+ }
+ }
+ ]
+}
diff --git a/ubuntu-ui/pages/elements/ChannelsGrid.qml b/ubuntu-ui/pages/elements/ChannelsGrid.qml
new file mode 100755
index 0000000..154a793
--- /dev/null
+++ b/ubuntu-ui/pages/elements/ChannelsGrid.qml
@@ -0,0 +1,100 @@
+/*
+ * Copyright © 2015-2016 Andrew Penkrat
+ *
+ * This file is part of TwitchTube.
+ *
+ * TwitchTube is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * TwitchTube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TwitchTube. If not, see .
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+import Ubuntu.Components.ListItems 1.3
+
+GridView {
+ id: grid
+
+ property alias channels: grid.model
+ property bool loadMoreAvailable: offset < totalCount
+ property int row: 3
+ // In brackets must be row lengths for portrait and landscape orientations
+ property int countOnPage: (2*3) * 3
+ property int offset: 0
+ property int totalCount: 0
+ property bool autoLoad: true
+ property var parameters: ({})
+
+ height: childrenRect.height
+
+ Component.onCompleted: {
+ if(autoLoad)
+ loadContent()
+ }
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ }
+ interactive: false
+
+ model: ListModel { id: channelsList }
+ cellWidth: width/row
+ // 5:8 is the actual aspect ratio of previews
+ cellHeight: cellWidth * 5/8
+
+ delegate: Empty {
+ id: delegate
+
+ width: grid.cellWidth
+ height: grid.cellHeight
+
+ onClicked: {
+ var properties = parameters
+ properties.channel = channel.name
+ pageStack.push (Qt.resolvedUrl("../StreamPage.qml"), properties)
+ }
+
+ Image {
+ id: previewImage
+
+ source: preview[channelImageSize.value]
+ anchors.fill: parent
+ anchors.margins: units.gu(1)
+ }
+
+ Label {
+ id: name
+
+ anchors {
+ left: previewImage.left; leftMargin: units.gu(2)
+ right: previewImage.right; rightMargin: units.gu(1)
+ topMargin: units.gu(1)
+ }
+ text: channel.display_name
+ color: UbuntuColors.lightGrey
+ }
+
+ Label {
+ id: title
+
+ visible: showBroadcastTitles.value
+ anchors {
+ left: previewImage.left; leftMargin: units.gu(2)
+ right: previewImage.right; rightMargin: units.gu(1)
+ top: name.bottom; topMargin: -units.gu(1)
+ }
+ text: channel.status
+ color: UbuntuColors.silk
+ }
+ }
+}
diff --git a/ubuntu-ui/pages/elements/GamesGrid.qml b/ubuntu-ui/pages/elements/GamesGrid.qml
new file mode 100755
index 0000000..8307a2e
--- /dev/null
+++ b/ubuntu-ui/pages/elements/GamesGrid.qml
@@ -0,0 +1,86 @@
+/*
+ * Copyright © 2015-2016 Andrew Penkrat
+ *
+ * This file is part of TwitchTube.
+ *
+ * TwitchTube is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * TwitchTube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TwitchTube. If not, see .
+ */
+
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+import Ubuntu.Components.ListItems 1.3
+
+GridView {
+ id: grid
+
+ property alias games: grid.model
+ property int row: 4
+ // In brackets must be row lengths for portrait and landscape orientations
+ property int countOnPage: (2*4) * 2
+ property int offset: 0
+ property int totalCount: 0
+ property bool autoLoad: true
+ property var parameters: ({})
+
+ Component.onCompleted: {
+ if(autoLoad)
+ loadContent()
+ }
+
+ height: childrenRect.height
+ anchors {
+ left: parent.left
+ right: parent.right
+ }
+
+ interactive: false
+
+ model: ListModel { id: gameList }
+ cellWidth: width/row
+ // 18:13 is the actual aspect ratio of previews
+ cellHeight: cellWidth * 18/13
+
+ delegate: Empty {
+ id: delegate
+
+ width: grid.cellWidth
+ height: grid.cellHeight
+ onClicked: {
+ var properties = parameters
+ properties.game = name
+ pageStack.push (Qt.resolvedUrl("../GameChannelsPage.qml"), properties)
+ }
+
+ Image {
+ id: logo
+
+ anchors.fill: parent
+ anchors.margins: units.gu(1)
+ fillMode: Image.PreserveAspectCrop
+ source: box[gameImageSize.value]
+ }
+
+ Label {
+ id: gameName
+
+ anchors {
+ left: parent.left; leftMargin: units.gu(2)
+ right: parent.right; rightMargin: units.gu(2)
+ topMargin: units.gu(1)
+ }
+ text: name
+ color: UbuntuColors.silk
+ }
+ }
+}
diff --git a/ubuntu-ui/pages/elements/GridWrapper.qml b/ubuntu-ui/pages/elements/GridWrapper.qml
new file mode 100755
index 0000000..79a1969
--- /dev/null
+++ b/ubuntu-ui/pages/elements/GridWrapper.qml
@@ -0,0 +1,37 @@
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+
+Flickable {
+ id: root
+
+ property alias grids: container.data
+ //property alias header: mainHeader
+
+ anchors.fill: parent
+ contentHeight: container.height
+
+ Column {
+ id: container
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.leftMargin: units.gu(2)
+ anchors.rightMargin: units.gu(2)
+
+// PageHeader {
+// id: mainHeader
+// }
+ }
+
+// PushUpMenu {
+// id: loadMoreMenu
+// enabled: grids[grids.length - 1].offset < grids[grids.length - 1].totalCount
+// visible: grids[grids.length - 1].offset < grids[grids.length - 1].totalCount
+
+// MenuItem {
+// text: qsTr("Load more")
+// onClicked: {
+// grids[grids.length - 1].loadContent()
+// }
+// }
+// }
+}
diff --git a/ubuntu/TwitchTube.apparmor b/ubuntu/TwitchTube.apparmor
new file mode 100644
index 0000000..51ef8ef
--- /dev/null
+++ b/ubuntu/TwitchTube.apparmor
@@ -0,0 +1,8 @@
+{
+ "policy_groups": [
+ "video",
+ "webview",
+ "networking"
+ ],
+ "policy_version": 1.3
+}
diff --git a/ubuntu/TwitchTube.desktop b/ubuntu/TwitchTube.desktop
new file mode 100644
index 0000000..2ba508b
--- /dev/null
+++ b/ubuntu/TwitchTube.desktop
@@ -0,0 +1,7 @@
+[Desktop Entry]
+Name=TwitchTube
+Exec=TwitchTube
+Icon=TwitchTube/TwitchTube.png
+Terminal=false
+Type=Application
+X-Ubuntu-Touch=true
diff --git a/ubuntu/TwitchTube.png b/ubuntu/TwitchTube.png
new file mode 100644
index 0000000..7e6d884
Binary files /dev/null and b/ubuntu/TwitchTube.png differ
diff --git a/ubuntu/manifest.json.in b/ubuntu/manifest.json.in
new file mode 100644
index 0000000..0c87e01
--- /dev/null
+++ b/ubuntu/manifest.json.in
@@ -0,0 +1,15 @@
+{
+ "name": "twitchtube.aldrog",
+ "description": "3rd party client for Twitch streaming service",
+ "architecture": "@CLICK_ARCH@",
+ "title": "TwitchTube",
+ "hooks": {
+ "TwitchTube": {
+ "apparmor": "TwitchTube/TwitchTube.apparmor",
+ "desktop": "TwitchTube/TwitchTube.desktop"
+ }
+ },
+ "version": "0.1",
+ "maintainer": "Andrew Penkrat ",
+ "framework": "ubuntu-sdk-15.04.5"
+}