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 var androidConnections: Connections {
target: (typeof(gpodderAndroid) === 'undefined') ? null : gpodderAndroid
target: platform.android ? gpodderAndroid : null
onAudioBecomingNoisy: {
if (playbackState === MediaPlayer.PlayingState) {

View file

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

View file

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

View file

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

View file

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

View file

@ -26,7 +26,7 @@ import 'common/constants.js' as Constants
import 'icons/icons.js' as Icons
SlidePage {
id: episodesPage
id: page
property int podcast_id
property string title
@ -44,7 +44,7 @@ SlidePage {
{
label: 'Mark episodes as old',
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',
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 () {
ctx.py.call('main.unsubscribe', [ctx.id]);
ctx.page.closePage();
@ -80,6 +80,6 @@ SlidePage {
EpisodeListView {
id: episodeList
title: episodesPage.title
title: page.title
}
}

View file

@ -27,7 +27,8 @@ ButtonArea {
property alias text: label.text
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 size: icon.size
property bool alwaysShowText: false

View file

@ -30,6 +30,7 @@ Item {
GPodderCore { id: py }
GPodderPlayback { id: player }
GPodderPlatform { id: platform }
GPodderPodcastListModel { id: podcastListModel }
GPodderPodcastListModelConnections {}
@ -98,6 +99,7 @@ Item {
property bool hasMenuButton: false
property string menuButtonLabel: ''
property string menuButtonIcon: ''
property string windowTitle: 'gPodder'
function topOfStackChanged(offset) {
if (offset === undefined) {
@ -107,9 +109,13 @@ Item {
var page = children[children.length+offset-1];
pgst.hasBackButton = Qt.binding(function () { return page.isDialog || page.canClose; });
pgst.hasMenuButton = Qt.binding(function () { return page.hasMenuButton; });
pgst.menuButtonLabel = Qt.binding(function () { return pgst.hasMenuButton ? page.menuButtonLabel : 'Menu'; });
pgst.menuButtonIcon = Qt.binding(function () { return pgst.hasMenuButton ? page.menuButtonIcon : Icons.vellipsis; });
pgst.hasMenuButton = Qt.binding(function () { return !page.isDialog && page.hasMenuButton; });
pgst.menuButtonLabel = Qt.binding(function () { return (!page.isDialog && pgst.hasMenuButton) ? page.menuButtonLabel : 'Menu'; });
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) {
@ -170,10 +176,11 @@ Item {
anchors {
left: parent.left
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
height: 10 * pgst.scalef
visible: toolbar.showing
@ -183,7 +190,14 @@ Item {
id: toolbar
z: 102
anchors {
top: platform.toolbarOnTop ? parent.top : undefined
bottom: platform.toolbarOnTop ? undefined : parent.bottom
}
Row {
id: toolbarButtonsLeft
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
@ -195,9 +209,7 @@ Item {
text: 'Back'
icon: Icons.arrow_left
// Don't show back button on Android (Android has its own)
visible: (typeof(gpodderAndroid) === 'undefined')
visible: platform.needsBackButton
enabled: pgst.hasBackButton
onClicked: {
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 {
id: toolbarButtonsRight
anchors {
verticalCenter: parent.verticalCenter
right: parent.right
@ -218,6 +245,7 @@ Item {
text: 'Now Playing'
icon: Icons.play
visible: !platform.floatingPlayButton
enabled: player.episode != 0
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 {
visible: py.ready
}

View file

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

View file

@ -36,7 +36,7 @@ Rectangle {
width: iconMenuItem.width
height: iconMenuItem.height
color: iconMenuItem.pressed ? Constants.colors.toolbarArea : 'transparent'
color: iconMenuItem.pressed ? (platform.invertedToolbar ? Constants.colors.toolbarArea : Constants.colors.inverted.toolbarArea) : 'transparent'
Rectangle {
height: 5 * pgst.scalef
@ -51,7 +51,8 @@ Rectangle {
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
enabled: parent.enabled
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
SlidePage {
id: podcastDetail
id: page
property int podcast_id
property string title
@ -43,7 +43,7 @@ SlidePage {
{
label: 'Visit website',
callback: function () {
Qt.openUrlExternally(podcastDetail.link);
Qt.openUrlExternally(page.link);
}
},
{
@ -51,14 +51,14 @@ SlidePage {
callback: function () {
pgst.loadPage('TextInputDialog.qml', {
placeholderText: 'Feed URL',
text: podcastDetail.url,
text: page.url,
});
}
},
{
label: 'Change section',
callback: function () {
var ctx = { py: py, id: podcastDetail.podcast_id };
var ctx = { py: py, id: page.podcast_id };
pgst.loadPage('TextInputDialog.qml', {
buttonText: 'Change section',
placeholderText: 'New section',
@ -74,18 +74,18 @@ SlidePage {
PBusyIndicator {
anchors.centerIn: parent
visible: !podcastDetail.ready
visible: !page.ready
}
Component.onCompleted: {
py.call('main.show_podcast', [podcast_id], function (podcast) {
podcastDetail.title = podcast.title;
podcastDetail.description = podcast.description;
podcastDetail.link = podcast.link;
podcastDetail.section = podcast.section;
podcastDetail.coverart = podcast.coverart;
podcastDetail.url = podcast.url;
podcastDetail.ready = true;
page.title = podcast.title;
page.description = podcast.description;
page.link = podcast.link;
page.section = podcast.section;
page.coverart = podcast.coverart;
page.url = podcast.url;
page.ready = true;
});
}
@ -100,7 +100,7 @@ SlidePage {
Column {
id: detailColumn
width: podcastDetail.width
width: page.width
spacing: Constants.layout.padding * pgst.scalef
Item { height: Constants.layout.padding * pgst.scalef; width: parent.width }
@ -116,23 +116,22 @@ SlidePage {
Image {
id: coverImage
source: podcastDetail.coverart
source: page.coverart
fillMode: Image.PreserveAspectFit
width: parent.width
}
}
PLabel {
text: podcastDetail.title
SlidePageHeader {
title: page.title
width: parent.width
wrapMode: Text.WordWrap
font.pixelSize: 35 * pgst.scalef
color: Constants.colors.highlight
}
PLabel {
visible: text !== ''
text: podcastDetail.link
text: page.link
width: parent.width
wrapMode: Text.WordWrap
font.pixelSize: 20 * pgst.scalef
@ -140,7 +139,7 @@ SlidePage {
}
PLabel {
text: 'Section: ' + podcastDetail.section
text: 'Section: ' + page.section
width: parent.width
wrapMode: Text.WordWrap
font.pixelSize: 20 * pgst.scalef
@ -148,7 +147,7 @@ SlidePage {
}
PLabel {
text: podcastDetail.description
text: page.description
width: parent.width
font.pixelSize: 30 * pgst.scalef
wrapMode: Text.WordWrap

View file

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

View file

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

View file

@ -27,9 +27,20 @@ Item {
id: slidePageHeader
property alias title: label.text
property alias color: label.color
property alias wrapMode: label.wrapMode
property bool isOnSlidePage: (typeof(page) !== 'undefined') ? page : null
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 {
id: label

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B