Android UI customizations

This commit is contained in:
Thomas Perl 2014-11-22 16:19:53 +01:00
parent c7fd930219
commit 9610c51c30
18 changed files with 260 additions and 48 deletions

View file

@ -0,0 +1,33 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2014, Thomas Perl <m@thp.io>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*
*/
import QtQuick 2.0
Item {
property bool emulatingAndroid: false
property bool android: (typeof(gpodderAndroid) !== 'undefined') || emulatingAndroid
property bool needsBackButton: !android
property bool toolbarOnTop: android
property bool invertedToolbar: toolbarOnTop
property bool titleInToolbar: toolbarOnTop
property bool floatingPlayButton: android
}

View file

@ -41,7 +41,7 @@ MediaPlayer {
property int playedFrom: 0 property int playedFrom: 0
property var androidConnections: Connections { property var androidConnections: Connections {
target: (typeof(gpodderAndroid) === 'undefined') ? null : gpodderAndroid target: platform.android ? gpodderAndroid : null
onAudioBecomingNoisy: { onAudioBecomingNoisy: {
if (playbackState === MediaPlayer.PlayingState) { if (playbackState === MediaPlayer.PlayingState) {

View file

@ -36,8 +36,17 @@ var colors = {
playback: '#729fcf', /* playback blue */ playback: '#729fcf', /* playback blue */
destructive: '#cf424f', /* destructive actions */ destructive: '#cf424f', /* destructive actions */
page: '#dddddd',
toolbar: '#d0d0d0', toolbar: '#d0d0d0',
toolbarText: '#333333',
toolbarDisabled: '#666666',
inverted: {
toolbar: '#815c86',
toolbarText: '#ffffff',
toolbarDisabled: '#aaffffff',
},
page: '#dddddd',
dialog: '#dddddd', dialog: '#dddddd',
dialogBackground: '#aa000000', dialogBackground: '#aa000000',
text: '#333333', /* text color */ text: '#333333', /* text color */

View file

@ -23,7 +23,7 @@ import QtQuick 2.0
import 'common/constants.js' as Constants import 'common/constants.js' as Constants
SlidePage { SlidePage {
id: aboutPage id: page
Flickable { Flickable {
id: flickable id: flickable
@ -36,7 +36,7 @@ SlidePage {
Column { Column {
id: detailColumn id: detailColumn
width: aboutPage.width width: page.width
spacing: 15 * pgst.scalef spacing: 15 * pgst.scalef
SlidePageHeader { SlidePageHeader {
@ -46,6 +46,8 @@ SlidePage {
Column { Column {
width: parent.width width: parent.width
Item { height: 10 * pgst.scalef; width: 1 }
PLabel { PLabel {
width: parent.width * .95 width: parent.width * .95
font.pixelSize: 30 * pgst.scalef font.pixelSize: 30 * pgst.scalef

View file

@ -75,11 +75,10 @@ SlidePage {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
spacing: Constants.layout.padding * pgst.scalef spacing: Constants.layout.padding * pgst.scalef
PLabel { SlidePageHeader {
text: detailPage.title title: detailPage.title
width: parent.width width: parent.width
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
font.pixelSize: 35 * pgst.scalef
color: Constants.colors.highlight color: Constants.colors.highlight
} }

View file

@ -26,7 +26,7 @@ import 'common/constants.js' as Constants
import 'icons/icons.js' as Icons import 'icons/icons.js' as Icons
SlidePage { SlidePage {
id: allEpisodesPage id: page
hasMenuButton: true hasMenuButton: true
menuButtonIcon: Icons.magnifying_glass menuButtonIcon: Icons.magnifying_glass

View file

@ -26,7 +26,7 @@ import 'common/constants.js' as Constants
import 'icons/icons.js' as Icons import 'icons/icons.js' as Icons
SlidePage { SlidePage {
id: episodesPage id: page
property int podcast_id property int podcast_id
property string title property string title
@ -44,7 +44,7 @@ SlidePage {
{ {
label: 'Mark episodes as old', label: 'Mark episodes as old',
callback: function () { callback: function () {
py.call('main.mark_episodes_as_old', [episodesPage.podcast_id]); py.call('main.mark_episodes_as_old', [page.podcast_id]);
}, },
}, },
{ {
@ -56,7 +56,7 @@ SlidePage {
{ {
label: 'Unsubscribe', label: 'Unsubscribe',
callback: function () { callback: function () {
var ctx = { py: py, id: episodesPage.podcast_id, page: episodesPage }; var ctx = { py: py, id: page.podcast_id, page: page };
pgst.showConfirmation(title, 'Unsubscribe', 'Cancel', 'Remove this podcast and all downloaded episodes?', Icons.trash, function () { pgst.showConfirmation(title, 'Unsubscribe', 'Cancel', 'Remove this podcast and all downloaded episodes?', Icons.trash, function () {
ctx.py.call('main.unsubscribe', [ctx.id]); ctx.py.call('main.unsubscribe', [ctx.id]);
ctx.page.closePage(); ctx.page.closePage();
@ -80,6 +80,6 @@ SlidePage {
EpisodeListView { EpisodeListView {
id: episodeList id: episodeList
title: episodesPage.title title: page.title
} }
} }

View file

@ -27,7 +27,8 @@ ButtonArea {
property alias text: label.text property alias text: label.text
property color color: Constants.colors.secondaryHighlight property color color: Constants.colors.secondaryHighlight
property color _real_color: enabled ? color : Constants.colors.placeholder property color colorDisabled: Constants.colors.placeholder
property color _real_color: enabled ? color : colorDisabled
property alias icon: icon.icon property alias icon: icon.icon
property alias size: icon.size property alias size: icon.size
property bool alwaysShowText: false property bool alwaysShowText: false

View file

@ -30,6 +30,7 @@ Item {
GPodderCore { id: py } GPodderCore { id: py }
GPodderPlayback { id: player } GPodderPlayback { id: player }
GPodderPlatform { id: platform }
GPodderPodcastListModel { id: podcastListModel } GPodderPodcastListModel { id: podcastListModel }
GPodderPodcastListModelConnections {} GPodderPodcastListModelConnections {}
@ -98,6 +99,7 @@ Item {
property bool hasMenuButton: false property bool hasMenuButton: false
property string menuButtonLabel: '' property string menuButtonLabel: ''
property string menuButtonIcon: '' property string menuButtonIcon: ''
property string windowTitle: 'gPodder'
function topOfStackChanged(offset) { function topOfStackChanged(offset) {
if (offset === undefined) { if (offset === undefined) {
@ -107,9 +109,13 @@ Item {
var page = children[children.length+offset-1]; var page = children[children.length+offset-1];
pgst.hasBackButton = Qt.binding(function () { return page.isDialog || page.canClose; }); pgst.hasBackButton = Qt.binding(function () { return page.isDialog || page.canClose; });
pgst.hasMenuButton = Qt.binding(function () { return page.hasMenuButton; }); pgst.hasMenuButton = Qt.binding(function () { return !page.isDialog && page.hasMenuButton; });
pgst.menuButtonLabel = Qt.binding(function () { return pgst.hasMenuButton ? page.menuButtonLabel : 'Menu'; }); pgst.menuButtonLabel = Qt.binding(function () { return (!page.isDialog && pgst.hasMenuButton) ? page.menuButtonLabel : 'Menu'; });
pgst.menuButtonIcon = Qt.binding(function () { return pgst.hasMenuButton ? page.menuButtonIcon : Icons.vellipsis; }); pgst.menuButtonIcon = Qt.binding(function () { return (!page.isDialog && pgst.hasMenuButton) ? page.menuButtonIcon : Icons.vellipsis; });
if (!page.isDialog) {
pgst.windowTitle = page.title || 'gPodder';
}
} }
function showConfirmation(title, affirmative, negative, description, icon, callback) { function showConfirmation(title, affirmative, negative, description, icon, callback) {
@ -170,10 +176,11 @@ Item {
anchors { anchors {
left: parent.left left: parent.left
right: parent.right right: parent.right
bottom: toolbar.top top: platform.toolbarOnTop ? toolbar.bottom : undefined
bottom: platform.toolbarOnTop ? undefined : toolbar.top
} }
source: 'images/toolbarshadow.png' source: platform.toolbarOnTop ? 'images/toolbarshadow-top.png' : 'images/toolbarshadow.png'
opacity: .1 opacity: .1
height: 10 * pgst.scalef height: 10 * pgst.scalef
visible: toolbar.showing visible: toolbar.showing
@ -183,7 +190,14 @@ Item {
id: toolbar id: toolbar
z: 102 z: 102
anchors {
top: platform.toolbarOnTop ? parent.top : undefined
bottom: platform.toolbarOnTop ? undefined : parent.bottom
}
Row { Row {
id: toolbarButtonsLeft
anchors { anchors {
verticalCenter: parent.verticalCenter verticalCenter: parent.verticalCenter
left: parent.left left: parent.left
@ -195,9 +209,7 @@ Item {
text: 'Back' text: 'Back'
icon: Icons.arrow_left icon: Icons.arrow_left
// Don't show back button on Android (Android has its own) visible: platform.needsBackButton
visible: (typeof(gpodderAndroid) === 'undefined')
enabled: pgst.hasBackButton enabled: pgst.hasBackButton
onClicked: { onClicked: {
if (enabled) { if (enabled) {
@ -207,7 +219,22 @@ Item {
} }
} }
PToolbarLabel {
visible: platform.titleInToolbar
anchors {
verticalCenter: parent.verticalCenter
left: toolbarButtonsLeft.right
right: toolbarButtonsRight.left
margins: Constants.layout.padding * pgst.scalef
}
text: pgst.windowTitle
}
Row { Row {
id: toolbarButtonsRight
anchors { anchors {
verticalCenter: parent.verticalCenter verticalCenter: parent.verticalCenter
right: parent.right right: parent.right
@ -218,6 +245,7 @@ Item {
text: 'Now Playing' text: 'Now Playing'
icon: Icons.play icon: Icons.play
visible: !platform.floatingPlayButton
enabled: player.episode != 0 enabled: player.episode != 0
onClicked: loadPage('PlayerPage.qml'); onClicked: loadPage('PlayerPage.qml');
@ -265,6 +293,45 @@ Item {
} }
} }
Rectangle {
z: 190
color: Constants.colors.playback
visible: platform.floatingPlayButton
Behavior on opacity { NumberAnimation { } }
opacity: (player.episode != 0) ? (player.isPlaying ? 1 : .5) : 0
width: Constants.layout.item.height * 1.1 * pgst.scalef
height: width
radius: height / 2
anchors {
right: parent.right
margins: Constants.layout.padding * 2 * pgst.scalef
}
y: pgst.height - height - anchors.margins
PIcon {
id: icon
anchors.centerIn: parent
icon: Icons.headphones
size: 60
color: Constants.colors.inverted.toolbarText
}
MouseArea {
anchors.fill: parent
onClicked: loadPage('PlayerPage.qml');
drag {
target: parent
axis: Drag.YAxis
minimumY: pgst.bottomSpacing + parent.anchors.margins
maximumY: pgst.height - parent.height - parent.anchors.margins
}
}
}
PodcastsPage { PodcastsPage {
visible: py.ready visible: py.ready
} }

View file

@ -28,7 +28,7 @@ Rectangle {
id: toolbar id: toolbar
property bool showing: true property bool showing: true
color: Constants.colors.toolbar color: platform.invertedToolbar ? Constants.colors.inverted.toolbar : Constants.colors.toolbar
height: 80 * pgst.scalef height: 80 * pgst.scalef
@ -40,9 +40,10 @@ Rectangle {
anchors { anchors {
left: parent.left left: parent.left
right: parent.right right: parent.right
bottom: parent.bottom topMargin: toolbar.showing ? 0 : -toolbar.height
bottomMargin: toolbar.showing ? 0 : -height bottomMargin: toolbar.showing ? 0 : -toolbar.height
} }
Behavior on anchors.bottomMargin { PropertyAnimation { duration: 100 } } Behavior on anchors.bottomMargin { PropertyAnimation { duration: 100 } }
Behavior on anchors.topMargin { PropertyAnimation { duration: 100 } }
} }

View file

@ -36,7 +36,7 @@ Rectangle {
width: iconMenuItem.width width: iconMenuItem.width
height: iconMenuItem.height height: iconMenuItem.height
color: iconMenuItem.pressed ? Constants.colors.toolbarArea : 'transparent' color: iconMenuItem.pressed ? (platform.invertedToolbar ? Constants.colors.toolbarArea : Constants.colors.inverted.toolbarArea) : 'transparent'
Rectangle { Rectangle {
height: 5 * pgst.scalef height: 5 * pgst.scalef
@ -51,7 +51,8 @@ Rectangle {
IconMenuItem { IconMenuItem {
id: iconMenuItem id: iconMenuItem
color: Constants.colors.text color: platform.invertedToolbar ? Constants.colors.inverted.toolbarText : Constants.colors.toolbarText
colorDisabled: platform.invertedToolbar ? Constants.colors.inverted.toolbarDisabled : Constants.colors.toolbarDisabled
transparent: true transparent: true
enabled: parent.enabled enabled: parent.enabled
onClicked: toolbarButton.clicked() onClicked: toolbarButton.clicked()

84
touch/PToolbarLabel.qml Normal file
View file

@ -0,0 +1,84 @@
/**
*
* gPodder QML UI Reference Implementation
* Copyright (c) 2014, Thomas Perl <m@thp.io>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*
*/
import QtQuick 2.0
import 'common'
import 'common/constants.js' as Constants
import 'icons/icons.js' as Icons
Item {
id: toolbarLabel
property string text: ''
property bool firstLable: false
onTextChanged: {
state = (state === 'a') ? 'b': 'a';
if (state === 'a') {
a.text = text;
} else {
b.text = text;
}
}
states: [
State {
name: 'a'
PropertyChanges { target: a; opacity: 1; anchors.leftMargin: 0 }
PropertyChanges { target: b; opacity: 0; anchors.leftMargin: 10 * pgst.scalef }
},
State {
name: 'b'
PropertyChanges { target: a; opacity: 0; anchors.leftMargin: -10 * pgst.scalef }
PropertyChanges { target: b; opacity: 1; anchors.leftMargin: 0 }
}
]
PLabel {
id: a
anchors {
left: parent.left
right: parent.right
verticalCenter: parent.verticalCenter
}
color: platform.invertedToolbar ? Constants.colors.inverted.toolbarText : Constants.colors.toolbarText
elide: Text.ElideRight
Behavior on anchors.leftMargin { NumberAnimation { } }
Behavior on opacity { NumberAnimation { } }
}
PLabel {
id: b
anchors {
left: parent.left
right: parent.right
verticalCenter: parent.verticalCenter
}
color: platform.invertedToolbar ? Constants.colors.inverted.toolbarText : Constants.colors.toolbarText
elide: Text.ElideRight
Behavior on anchors.leftMargin { NumberAnimation { } }
Behavior on opacity { NumberAnimation { } }
}
}

View file

@ -25,7 +25,7 @@ import 'common/util.js' as Util
import 'icons/icons.js' as Icons import 'icons/icons.js' as Icons
SlidePage { SlidePage {
id: podcastDetail id: page
property int podcast_id property int podcast_id
property string title property string title
@ -43,7 +43,7 @@ SlidePage {
{ {
label: 'Visit website', label: 'Visit website',
callback: function () { callback: function () {
Qt.openUrlExternally(podcastDetail.link); Qt.openUrlExternally(page.link);
} }
}, },
{ {
@ -51,14 +51,14 @@ SlidePage {
callback: function () { callback: function () {
pgst.loadPage('TextInputDialog.qml', { pgst.loadPage('TextInputDialog.qml', {
placeholderText: 'Feed URL', placeholderText: 'Feed URL',
text: podcastDetail.url, text: page.url,
}); });
} }
}, },
{ {
label: 'Change section', label: 'Change section',
callback: function () { callback: function () {
var ctx = { py: py, id: podcastDetail.podcast_id }; var ctx = { py: py, id: page.podcast_id };
pgst.loadPage('TextInputDialog.qml', { pgst.loadPage('TextInputDialog.qml', {
buttonText: 'Change section', buttonText: 'Change section',
placeholderText: 'New section', placeholderText: 'New section',
@ -74,18 +74,18 @@ SlidePage {
PBusyIndicator { PBusyIndicator {
anchors.centerIn: parent anchors.centerIn: parent
visible: !podcastDetail.ready visible: !page.ready
} }
Component.onCompleted: { Component.onCompleted: {
py.call('main.show_podcast', [podcast_id], function (podcast) { py.call('main.show_podcast', [podcast_id], function (podcast) {
podcastDetail.title = podcast.title; page.title = podcast.title;
podcastDetail.description = podcast.description; page.description = podcast.description;
podcastDetail.link = podcast.link; page.link = podcast.link;
podcastDetail.section = podcast.section; page.section = podcast.section;
podcastDetail.coverart = podcast.coverart; page.coverart = podcast.coverart;
podcastDetail.url = podcast.url; page.url = podcast.url;
podcastDetail.ready = true; page.ready = true;
}); });
} }
@ -100,7 +100,7 @@ SlidePage {
Column { Column {
id: detailColumn id: detailColumn
width: podcastDetail.width width: page.width
spacing: Constants.layout.padding * pgst.scalef spacing: Constants.layout.padding * pgst.scalef
Item { height: Constants.layout.padding * pgst.scalef; width: parent.width } Item { height: Constants.layout.padding * pgst.scalef; width: parent.width }
@ -116,23 +116,22 @@ SlidePage {
Image { Image {
id: coverImage id: coverImage
source: podcastDetail.coverart source: page.coverart
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
width: parent.width width: parent.width
} }
} }
PLabel { SlidePageHeader {
text: podcastDetail.title title: page.title
width: parent.width width: parent.width
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
font.pixelSize: 35 * pgst.scalef
color: Constants.colors.highlight color: Constants.colors.highlight
} }
PLabel { PLabel {
visible: text !== '' visible: text !== ''
text: podcastDetail.link text: page.link
width: parent.width width: parent.width
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
font.pixelSize: 20 * pgst.scalef font.pixelSize: 20 * pgst.scalef
@ -140,7 +139,7 @@ SlidePage {
} }
PLabel { PLabel {
text: 'Section: ' + podcastDetail.section text: 'Section: ' + page.section
width: parent.width width: parent.width
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
font.pixelSize: 20 * pgst.scalef font.pixelSize: 20 * pgst.scalef
@ -148,7 +147,7 @@ SlidePage {
} }
PLabel { PLabel {
text: podcastDetail.description text: page.description
width: parent.width width: parent.width
font.pixelSize: 30 * pgst.scalef font.pixelSize: 30 * pgst.scalef
wrapMode: Text.WordWrap wrapMode: Text.WordWrap

View file

@ -26,7 +26,8 @@ import 'icons/icons.js' as Icons
import 'common/constants.js' as Constants import 'common/constants.js' as Constants
SlidePage { SlidePage {
id: podcastsPage id: page
canClose: false canClose: false
hasMenuButton: true hasMenuButton: true

View file

@ -33,6 +33,7 @@ Rectangle {
property alias canClose: dragging.canClose property alias canClose: dragging.canClose
property bool isDialog: false property bool isDialog: false
property string title: ''
property bool hasMenuButton: false property bool hasMenuButton: false
property string menuButtonLabel: 'Menu' property string menuButtonLabel: 'Menu'
property string menuButtonIcon: Icons.vellipsis property string menuButtonIcon: Icons.vellipsis
@ -49,6 +50,8 @@ Rectangle {
width: parent.width width: parent.width
height: parent.height - parent.bottomSpacing height: parent.height - parent.bottomSpacing
y: platform.toolbarOnTop ? parent.bottomSpacing : 0
Stacking { id: stacking } Stacking { id: stacking }
Dragging { Dragging {

View file

@ -27,9 +27,20 @@ Item {
id: slidePageHeader id: slidePageHeader
property alias title: label.text property alias title: label.text
property alias color: label.color property alias color: label.color
property alias wrapMode: label.wrapMode
property bool isOnSlidePage: (typeof(page) !== 'undefined') ? page : null
width: parent.width width: parent.width
height: Constants.layout.header.height * pgst.scalef
visible: !platform.titleInToolbar || !isOnSlidePage
height: visible ? (Constants.layout.header.height * pgst.scalef) : 0
Binding {
target: isOnSlidePage ? page : null
property: 'title'
value: slidePageHeader.title
when: platform.titleInToolbar
}
PLabel { PLabel {
id: label id: label

View file

@ -22,3 +22,4 @@ var link = '\ue077';
var vellipsis = '\u22ee'; var vellipsis = '\u22ee';
var paperclip = '\ue08a'; var paperclip = '\ue08a';
var tag_fill = '\ue02b'; var tag_fill = '\ue02b';
var headphones = '\ue061';

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B