diff --git a/Changelog b/Changelog index 23cd512..395a850 100644 --- a/Changelog +++ b/Changelog @@ -1,7 +1,10 @@ 0.3 - Landscape orientation enabled. - Variable screen size now works. -- Load the last timer value on program start. +- Optionally load the last timer value on program start. +- Use MusicPickerPage for selected alarm sound file. +- Predefined timers were too wide. +- Couldn't change predefined sec/min to 0. 0.2.1 - Timer didn't show on Sailfish OS 1.1.9. diff --git a/qml/cover/CoverPage.qml b/qml/cover/CoverPage.qml index 3c5f17e..b01e5ab 100644 --- a/qml/cover/CoverPage.qml +++ b/qml/cover/CoverPage.qml @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-15 Thomas Tanghus + Copyright (C) 2013-19 Thomas Tanghus All rights reserved. You may use this file under the terms of BSD license as follows: @@ -46,7 +46,7 @@ CoverBackground { truncationMode: TruncationMode.Fade; horizontalAlignment: Text.AlignHCenter; width: parent.width; - font.pixelSize: Theme.fontSizeLarge; + font.pixelSize: Theme.fontSizeExtraLarge; } Label { text: timeText; diff --git a/qml/harbour-kitchentimer.qml b/qml/harbour-kitchentimer.qml index 3114f6f..a132637 100644 --- a/qml/harbour-kitchentimer.qml +++ b/qml/harbour-kitchentimer.qml @@ -30,7 +30,6 @@ import QtQuick 2.0 import QtMultimedia 5.6 import Sailfish.Silica 1.0 -//import Sailfish.Media 1.0 import harbour.kitchentimer.insomniac 1.0 import "pages" import "cover" @@ -40,163 +39,188 @@ ApplicationWindow { id: app; - property string timeText: '00:01'; - property bool useDefaultSound: true; - property bool loadLast: true; - property bool loopSound: true; - property string builtinSound: Qt.resolvedUrl('../sounds/harbour-kitchentimer.wav'); - property string selectedSound: builtinSound; - property bool isBusy: false; - // Close enough to assume screen is off. + property string timeText: "00:00" + property int timersAlignment: Dock.Left + property bool loadLast: true + property bool useDefaultSound: true + property bool loopSound: true + property string builtinSound: Qt.resolvedUrl("../sounds/harbour-kitchentimer.wav") + property string selectedSound + property bool isBusy: false + // Close enough to determine whether screen is on or off. property bool viewable: cover.status === Cover.Active || cover.status === Cover.Activating - || applicationActive; - property bool isPlaying: alarm.playbackState === Audio.PlayingState; - property bool isRunning: timer.running || insomniac.running; - property alias seconds: timerPage.seconds; - property alias minutes: timerPage.minutes; - property int lastTimerMin: -1; - property int lastTimerSec: -1; - property int _lastTick: 0; + || applicationActive + property bool isPlaying: alarm.playbackState === Audio.PlayingState + property bool isRunning: timer.running || insomniac.running + property alias seconds: timerPage.seconds + property alias minutes: timerPage.minutes + property int lastTimerMin: -1 + property int lastTimerSec: -1 + property int _lastTick: 0 // Remaining time in seconds when screen blanks - property int _remaining: 0; + property int _remaining: 0 - allowedOrientations: Orientation.Portrait | Orientation.Landscape; //defaultAllowedOrientations + allowedOrientations: Orientation.Portrait | Orientation.Landscape //defaultAllowedOrientations onViewableChanged: { if(!isRunning) { - return; + return } if(viewable) { - wakeUp(); + wakeUp() } else { - snooze(); + snooze() } - } Component.onCompleted: { - load(); + load() + } + + Component.onDestruction: { + // Apparently this is never called + console.log("ApplicationWindow.onDestruction:") + saveSettings() + saveTimers() } initialPage: TimerPage { - id: timerPage; + id: timerPage } cover: CoverPage { - id: cover; + id: cover } BusyIndicator { - id: busyIndicator; - anchors.centerIn: parent; - size: BusyIndicatorSize.Large; + id: busyIndicator + anchors.centerIn: parent + size: BusyIndicatorSize.Large } ListModel { - id: timersModel; + id: timersModel } Audio { - id: alarm; - loops: loopSound ? Audio.Infinite : 1; - source: useDefaultSound ? builtinSound : Qt.resolvedUrl(selectedSound); - audioRole: Audio.AlarmRole; + id: alarm + loops: loopSound ? Audio.Infinite : 1 + source: useDefaultSound ? builtinSound : Qt.resolvedUrl(selectedSound) + audioRole: Audio.AlarmRole onError: { console.log("Audio error:", errorString, selectedSound) } } Timer { - id: timer; - interval: 1000; + id: timer + interval: 1000 running: false; repeat: true; onTriggered: { - var now = Math.round(Date.now()/1000); - seconds -= now - _lastTick; - _lastTick = now; - //console.log('seconds', seconds); + var now = Math.round(Date.now()/1000) + seconds -= now - _lastTick + _lastTick = now if(minutes === 0 && seconds === 0) { - reset(); - playAlarm(); + reset() + playAlarm() } } } Timer { - id: wakeupTimer; - interval: 1000; + id: wakeupTimer + interval: 1000 running: false; repeat: false; onTriggered: { - alarm.play(); - app.activate(); + alarm.play() + app.activate() pageStack.pop(timerPage) } } Insomniac { - id: insomniac; - repeat: false; - timerWindow: 10; + id: insomniac + repeat: false + timerWindow: 10 onTimeout: { - wakeUp(); + wakeUp() } onError: { - console.warn('Error in wake-up timer'); + console.warn("Error in wake-up timer") } } Storage { - id: storage; - dbName: StandardPaths.data; + id: storage + dbName: StandardPaths.data } Connections { - target: cover; - onMute: mute(); - onPause: pause(); - onReset: reset(); - onStart: start(); + target: cover + onMute: mute() + onPause: pause() + onReset: reset() + onStart: start() + } + + function saveSettings() { + setBusy(true) + + // The values are assigned in timerPage.onAccept + settings.setValue("timersAlignment", timersAlignment) + settings.setValue("loopSound", loopSound) + settings.setValue("loadLast", loadLast) + settings.setValue("useDefaultSound", useDefaultSound) + + if(!useDefaultSound) { + settings.setValue("selectedSound", selectedSound) + } + setBusy(false) } - function save() { - setBusy(true); - var timers = []; + function saveTimers() { + setBusy(true) + var timers = [] for (var i = 0; i < timersModel.count; ++i) { - var timer = timersModel.get(i); - timers.push({name:timer.name, minutes: timer.minutes, seconds:timer.seconds}); + var timer = timersModel.get(i) + timers.push({name:timer.name, minutes: timer.minutes, seconds:timer.seconds}) } - storage.saveTimers(timers); - setBusy(false); + storage.saveTimers(timers) + setBusy(false) } function load() { - setBusy(true); - - loopSound = settings.value('loopSound', true); - loadLast = settings.value('loadLast', true) - useDefaultSound = settings.value('useDefaultSound', true); - console.log("Default sound?", useDefaultSound) - selectedSound = useDefaultSound ? builtinSound : settings.value('selectedSound', builtinSound); - console.log("Selected sound:", selectedSound) + setBusy(true) + + loopSound = settings.value("loopSound", true) + timersAlignment = settings.value("timersAlignment", Dock.Left) + loadLast = settings.value("loadLast", true) + useDefaultSound = settings.value("useDefaultSound", true) + selectedSound = settings.value("selectedSound", "") if(loadLast) { - minutes = lastTimerMin = settings.value("lastTimerMin", -1); - seconds = lastTimerSec = settings.value("lastTimerSec", -1); + minutes = lastTimerMin = settings.value("lastTimerMin", -1) + seconds = lastTimerSec = settings.value("lastTimerSec", -1) } + loadTimers() + // For some odd reason the app isn't set to active on load..? //app.activate(); - applicationActive = true; - showTime(); + applicationActive = true + showTime() + setBusy(false) + } - var timers = storage.getTimers(); + function loadTimers() { + var timers = storage.getTimers() if(timers === false) { - console.warn('Default timers could not be loaded'); - setBusy(false); + console.warn("Default timers could not be loaded") + setBusy(false) return } @@ -207,110 +231,123 @@ ApplicationWindow { minutes: timers[i].minutes, seconds: timers[i].seconds } - ); + ) } - setBusy(false); } - function reload() { - timersModel.clear(); - load(); + function reloadTimers() { + timersModel.clear() + loadTimers() } function setBusy(state) { - isBusy = state; - busyIndicator.running = state; + isBusy = state + busyIndicator.running = state } function showTime() { if(!viewable) { - return; + return } - timeText = Qt.formatTime(new Date(0, 0, 0, 0, minutes, seconds), 'mm:ss'); + timeText = Qt.formatTime(new Date(0, 0, 0, 0, minutes, seconds), 'mm:ss') } function setTime(mins, secs) { - minutes = mins; - seconds = secs; + minutes = mins + seconds = secs } function mute() { if(alarm.playbackState === Audio.PlayingState) { - alarm.stop(); + alarm.stop() } } function pause() { if(timer.running) { - timer.stop(); + timer.stop() } } function reset() { if(timer.running) { - timer.stop(); + timer.stop() } if(insomniac.running) { - insomniac.stop(); + insomniac.stop() } - seconds = minutes = _remaining = _lastTick = 0; + seconds = minutes = _remaining = _lastTick = 0 } function start() { if(!timer.running) { - _lastTick = Math.round(Date.now()/1000); - lastTimerMin = minutes; - lastTimerSec = seconds; - settings.setValue("lastTimerMin", lastTimerMin); - settings.setValue("lastTimerSec", lastTimerSec); - timer.start(); + _lastTick = Math.round(Date.now()/1000) + lastTimerMin = minutes + lastTimerSec = seconds + settings.setValue("lastTimerMin", lastTimerMin) + settings.setValue("lastTimerSec", lastTimerSec) + timer.start() } } function snooze() { - timer.stop(); - _remaining = seconds + (minutes * 60); - _lastTick = Math.round(Date.now()/1000); + timer.stop() + _remaining = seconds + (minutes * 60) + _lastTick = Math.round(Date.now()/1000) // Subtract 10 seconds for timer window - insomniac.interval =_remaining - 10; - insomniac.start(); + insomniac.interval =_remaining - 10 + insomniac.start() } function wakeUp() { if(insomniac.running) { - insomniac.stop(); + insomniac.stop() } - var now = Math.round(Date.now()/1000); - var passed = now - _lastTick; - _lastTick = now; + var now = Math.round(Date.now()/1000) + var passed = now - _lastTick + _lastTick = now if(passed >= _remaining) { - console.warn('Time has passed!', passed - _remaining, 'seconds'); - reset(); - playAlarm(); + console.warn('Time has passed!', passed - _remaining, 'seconds') + reset() + playAlarm() } else { - timer.start(); - _remaining = _remaining - passed; + timer.start() + _remaining = _remaining - passed if(_remaining > 60) { - minutes = Math.floor(_remaining/60); - seconds = Math.round(_remaining - (minutes*60)); + minutes = Math.floor(_remaining/60) + seconds = Math.round(_remaining - (minutes*60)) } else { - minutes = 0; - seconds = _remaining; + minutes = 0 + seconds = _remaining } } } function playAlarm() { - display.unBlank(); + display.unBlank() if(display.isLocked()) { - display.unLock(); + display.unLock() } // Apparently Lipstick(?) needs some time before you can activate the app. - wakeupTimer.start(); + wakeupTimer.start() } -} + function formatTime(text) { + var t = parseInt(text), newText + // If the delegate hasn't instantiated yet - I guess... + if(t === NaN) { + return "00" + } + + // I'd like to do this in a READABLE one-liner + // Make sure that time is not more than 59 mins. and 59 secs + newText = t < 60 ? String(t) : "59" + // Format time '0' => '00', '9' => '09' etc. + newText = t >= 10 ? String(t) : "0" + String(t) + return newText + } +} diff --git a/qml/pages/AboutPage.qml b/qml/pages/AboutPage.qml index ef094fe..af158c5 100644 --- a/qml/pages/AboutPage.qml +++ b/qml/pages/AboutPage.qml @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-15 Thomas Tanghus + Copyright (C) 2013-19 Thomas Tanghus All rights reserved. You may use this file under the terms of BSD license as follows: @@ -46,6 +46,7 @@ Page { y: Theme.paddingLarge anchors.top: header.bottom; anchors.horizontalCenter: parent.horizontalCenter + width: 128; height: 128 //opacity: 0.4 source: "image://theme/harbour-kitchentimer" } diff --git a/qml/pages/SettingsDialog.qml b/qml/pages/SettingsDialog.qml index f3a44ca..3647f7e 100644 --- a/qml/pages/SettingsDialog.qml +++ b/qml/pages/SettingsDialog.qml @@ -1,5 +1,5 @@ /* - Copyright (C) 2015 Thomas Tanghus + Copyright (C) 2015-2019 Thomas Tanghus All rights reserved. You may use this file under the terms of BSD license as follows: @@ -32,6 +32,7 @@ import QtMultimedia 5.6 import Sailfish.Silica 1.0 import Sailfish.Pickers 1.0 import Sailfish.Media 1.0 +//import "../components" Dialog { id: settingsDialog; @@ -42,6 +43,8 @@ Dialog { property bool tmpUseDefaultSound: useDefaultSound; property bool tmpLoopSound: loopSound; property bool tmpLoadLast: loadLast; + property int tmpTimersAlignment: timersAlignment + property alias timersAlignmentText: alignmentCombo.value //canAccept: useDefaultSound || tmpSelectedSound !== selectedSound; @@ -54,83 +57,125 @@ Dialog { DialogHeader { id: header; dialog: settingsDialog; - title: qsTr('Settings'); + title: qsTr("Settings"); } - + Column { id: column; - + y: header.height + Theme.paddingMedium; //height: parent.height - (header.height + Theme.paddingMedium); anchors.leftMargin: Theme.paddingLarge; anchors.rightMargin: Theme.paddingLarge; width: settingsDialog.width; spacing: Theme.horizontalPageMargin; - + TextSwitch { id: doLoadLast; checked: loadLast; x: Theme.paddingLarge; - text: qsTr('Load last timer'); - description: qsTr('Reload the last timer when starting the app'); + text: qsTr("Load last timer"); + description: qsTr("Reload the last timer when starting the app"); onCheckedChanged: { - console.log('LoadLast', checked) + //console.log("LoadLast", checked) tmpLoadLast = checked; } } - + + ComboBox { + id: alignmentCombo + label: qsTr("Timers menu alignment") + description: qsTr("Select to which side of the screen the predefined timers menu should be placed") + onCurrentIndexChanged: { + var alignmentObject = alignmentModel.get(currentIndex) + value = alignmentObject.align + tmpTimersAlignment = alignmentObject.value + timersAlignmentText = alignmentObject.align + } + + Component.onCompleted: { + currentIndex = getIndex(timersAlignment) + value = alignmentModel.get(currentIndex).align + } + + menu: ContextMenu { + Repeater { + model: alignmentModel + delegate: MenuItem { + Label { + text: model.align + } + } + } + ListModel { + id: alignmentModel + ListElement { align: qsTr("Left"); value: Dock.Left } + ListElement { align: qsTr("Right"); value: Dock.Right } + } + } + + function getIndex(value) { + for(var i = 0; i < alignmentModel.count; i++) { + if(value === alignmentModel.get(i).value) { + console.log("Got it:", value) + return i + } + } + } + } + Label { text: qsTr("Alarm sound") x: Theme.paddingLarge; color: Theme.highlightColor - font.family: Theme.fontFamilyHeading } + font.family: Theme.fontFamilyHeading + } TextSwitch { id: doLoop; checked: loopSound; x: Theme.paddingLarge; - text: qsTr('Loop alarm sound'); - description: qsTr('Repeat alarm sound until you stop it'); + text: qsTr("Loop alarm sound"); + description: qsTr("Repeat alarm sound until you stop it"); onCheckedChanged: { - console.log('Loop', checked) + console.log("Loop", checked) tmpLoopSound = checked; } } - + TextSwitch { id: soundSelector; checked: useDefaultSound; x: Theme.paddingLarge; - text: qsTr('Default sound'); - description: qsTr('Use the alarm sound provided by the app'); + text: qsTr("Default sound"); + description: qsTr("Use the alarm sound provided by the app"); onCheckedChanged: { - console.log('useDefaultSound', checked); + console.log("useDefaultSound", checked); tmpUseDefaultSound = checked; if(checked) { sound.source = builtinSound; //tmpSelectedSound = builtinSound; - console.log('Using builtinSound', builtinSound); + console.log("Using builtinSound", builtinSound); } } } - + /*TextSwitch { id: doVibrate; checked: vibrate; x: Theme.paddingLarge; - text: qsTr('Vibrate'); - description: 'Since QtFeedback is not yet allowed, this does nothing.'; + text: qsTr("Vibrate"); + description: "Since QtFeedback is not yet allowed, this does nothing."; onCheckedChanged: { - console.log('Vibrate', checked) + console.log("Vibrate", checked) vibrate = checked; } }*/ - + ValueButton { enabled: !tmpUseDefaultSound; - label: qsTr('Select music file') + label: qsTr("Select music file") value: baseName(tmpSelectedSound) - //enabled: !soundSelector.checked onClicked: { pageStack.push(musicPickerPage); } @@ -164,44 +209,38 @@ Dialog { } onAccepted: { - loopSound = tmpLoopSound; - settings.setValue('loopSound', loopSound); + timersAlignment = tmpTimersAlignment + loopSound = tmpLoopSound loadLast = tmpLoadLast - settings.setValue('loadLast', loadLast); - - useDefaultSound = tmpUseDefaultSound; - settings.setValue('useDefaultSound', useDefaultSound); - - if(!useDefaultSound) { - selectedSound = tmpSelectedSound; - console.log("Saving not default sound", selectedSound) - settings.setValue('selectedSound', selectedSound); - } + useDefaultSound = tmpUseDefaultSound + selectedSound = tmpSelectedSound + + // The settings should be saved in ApplicationWindow.onDestruction, + // but that isn't called when closing an app + saveSettings() } - + Component { id: musicPickerPage MusicPickerPage { onSelectedContentPropertiesChanged: { tmpSelectedSound = selectedContentProperties.filePath - sound.source = tmpSelectedSound; - console.log("Selected:", tmpSelectedSound); + sound.source = tmpSelectedSound } } } Audio { - id: sound; - loops: tmpLoopSound ? Audio.Infinite : 1; - source: selectedSound; - audioRole: Audio.AlarmRole; + id: sound + loops: tmpLoopSound ? Audio.Infinite : 1 + source: selectedSound + audioRole: Audio.AlarmRole onError: { console.log("Audio error:", errorString, selectedSound) } } function baseName(path) { - return path.split(/[\\/]/).pop(); + return path.split(/[\\/]/).pop() } } - diff --git a/qml/pages/TimerPage.qml b/qml/pages/TimerPage.qml index 0b36435..db86750 100644 --- a/qml/pages/TimerPage.qml +++ b/qml/pages/TimerPage.qml @@ -35,85 +35,72 @@ Page { id: timerPage; allowedOrientations: Orientation.All; - property alias seconds: kitchenTimer.seconds; - property alias minutes: kitchenTimer.minutes; - property Item contextMenu; + property alias seconds: kitchenTimer.seconds + property alias minutes: kitchenTimer.minutes + property Item contextMenu Component.onCompleted: { - showTime(); + showTime() } onSecondsChanged: { - showTime(); + showTime() } onMinutesChanged: { - showTime(); + showTime() } SilicaFlickable { - anchors.fill: parent; - - //onWidthChanged: { - // console.log(orientation === Orientation.Portrait ? "Portrait" : "Landscape") - //} + anchors.fill: parent PullDownMenu { MenuItem { - text: qsTr('About'); + text: qsTr("About") onClicked: { - pageStack.push(Qt.resolvedUrl('AboutPage.qml')); + pageStack.push(Qt.resolvedUrl("AboutPage.qml")) } } MenuItem { - text: qsTr('Edit default timers'); - onClicked: pageStack.push(Qt.resolvedUrl('TimersDialog.qml')); + text: qsTr("Edit default timers") + onClicked: pageStack.push(Qt.resolvedUrl("TimersDialog.qml")) } MenuItem { - text: qsTr('Settings'); - onClicked: pageStack.push(Qt.resolvedUrl('SettingsDialog.qml')); + text: qsTr("Settings") + onClicked: pageStack.push(Qt.resolvedUrl("SettingsDialog.qml")) } MenuItem { - text: qsTr('Last timer:') - + ' ' + (lastTimerMin >= 10 ? lastTimerMin : '0' + String(lastTimerMin)) + ':' - + (lastTimerSec >= 10 ? lastTimerSec : '0' + String(lastTimerSec)); + text: qsTr("Last timer:") + + " " + (lastTimerMin >= 10 ? lastTimerMin : "0" + String(lastTimerMin)) + ":" + + (lastTimerSec >= 10 ? lastTimerSec : "0" + String(lastTimerSec)) onClicked: { - setTime(lastTimerMin, lastTimerSec); + setTime(lastTimerMin, lastTimerSec) } - visible: lastTimerMin !== -1 && lastTimerSec !== -1; + visible: lastTimerMin !== -1 && lastTimerSec !== -1 } } PushUpMenu { - visible: timersModel.count > 0; - Repeater { - model: timersModel; - delegate: MenuItem { - text: model.name + ' ' - + (model.minutes>= 10 ? model.minutes : '0' + String(model.minutes)) - + ':' - + (model.seconds >= 10 ? model.seconds : '0' + String(model.seconds)); - onClicked: { - setTime(model.minutes, model.seconds); - console.log('Selected timer', model.name); - } - } + visible: !drawer.open + MenuItem { + text: qsTr("Timers") + onClicked: drawer.open = true } } // Tell SilicaFlickable the height of its content. - contentHeight: column.height; + contentHeight: column.height Column { id: column; - width: Screen.width - (Theme.paddingLarge * 2); - spacing: Theme.paddingLarge; - anchors.centerIn: parent; + width: Screen.width - (Theme.paddingLarge * 2) + spacing: Theme.paddingLarge + anchors.centerIn: parent PageHeader { - id: header; - title: qsTr('Kitchen Timer'); - visible: timerPage.isPortrait ? true : false; + id: header + title: qsTr("Kitchen Timer") + visible: timerPage.isPortrait ? true : false } // Dummy element to create some top spacing when in Landscape and to center @@ -121,22 +108,28 @@ Page { Item { height: header.visible ? (Screen.height/2)-(kitchenTimer.height/2)-header.height-(Theme.paddingLarge*2) : - Theme.paddingLarge; - width: parent.width; + Theme.paddingLarge + width: parent.width } Item { - width: column.width - (Theme.paddingLarge * 2); - height : width; - anchors.horizontalCenter: parent.horizontalCenter; + width: column.width - (Theme.paddingLarge * 2) + height: width + anchors.horizontalCenter: parent.horizontalCenter KitchenTimer { - id: kitchenTimer; - anchors.centerIn: parent; + id: kitchenTimer + anchors.centerIn: parent } BackgroundItem { id: timerButton; property alias text: timerButtonLabel.text + + drag.target: drawer + drag.axis: Drag.XAxis + drag.minimumX: 10 + drag.maximumX: timerPage.width // - rect.width + anchors.centerIn: kitchenTimer; width: timerButtonLabel.width + (Theme.paddingLarge*2) height: timerButtonLabel.height @@ -169,14 +162,21 @@ Page { } } + onPositionChanged: { + console.log("Dragging?", x, y) + } + onPressedChanged: { - if (pressed) { + console.log("Press changed", x, y) + /* + if(pressed) { pressTimer.start() - } + }*/ } + onCanceled: { - timerButton.DragFilter.end() - pressTimer.stop() + /*timerButton.DragFilter.end() + pressTimer.stop()*/ } onClicked: { @@ -188,7 +188,9 @@ Page { start(); } } + onPressAndHold: { + console.log("pressAndHold") setMenuModel(); if((minutes === 0 && seconds === 0) & !isPlaying && !isRunning) { return; @@ -204,7 +206,7 @@ Page { } ListModel { - id: menuModel; + id: menuModel } Timer { @@ -213,18 +215,84 @@ Page { } Component { - id: contextMenuComponent; + id: contextMenuComponent ContextMenu { + container: timerButton Repeater { - id: menuRepeater; - model: menuModel; + id: menuRepeater + model: menuModel delegate: MenuItem { - text: model.name; - onClicked: { - //console.log('Action:', model.action); - runMenuAction(model.action); - } + text: model.name + onClicked: runMenuAction(model.action) + } + } + } + } + } // end Flickable + + // Close drawer when tapped outside of it + MouseArea { + visible: drawer.open + anchors.fill: parent + onClicked: drawer.open = false + } + + Drawer { + id: drawer + + open: false + dock: timersAlignment + + anchors.fill: parent + hideOnMinimize: true + Drag.active: timerButton.drag.active + //Drag.hotSpot.x: 10 + //Drag.hotSpot.y: 10 + + background: Rectangle { + anchors.fill: parent + color: Theme.rgba(Theme.highlightBackgroundColor, Theme.highlightBackgroundOpacity) + SilicaListView { + id: timersList + y: header.height + width: parent.width + height: parent.height - header.height + contentHeight: timersModel.count * Theme.itemSizeLarge + + VerticalScrollDecorator { + } + + model: timersModel + + delegate: ListItem { + width: parent.width - Theme.horizontalPageMargin + Label { + id: timerNameLabel + truncationMode: TruncationMode.Fade + x: Theme.horizontalPageMargin + font.pixelSize: Theme.fontSizeSmall + text: model.name + anchors.verticalCenter: parent.verticalCenter + horizontalAlignment: Text.AlignLeft + width: parent.width/2 + //color: parent.highlighted ? Theme.highlightColor : Theme.primaryColor + color: Theme.primaryColor + } + + Label { + anchors.left: timerNameLabel.right + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + font.pixelSize: Theme.fontSizeSmall + horizontalAlignment: Text.AlignRight + text:formatTime(model.minutes) + ":" + formatTime(model.seconds) + color: Theme.primaryColor + } + + onClicked: { + drawer.open = false + setTime(model.minutes, model.seconds) } } } @@ -233,43 +301,40 @@ Page { function runMenuAction(action) { switch(action) { - case 'start': - start(); - break; - case 'reset': - reset(); - break; - case 'mute': - mute(); - break; - case 'pause': - pause(); - break; + case "start": + start() + break + case "reset": + reset() + break + case "mute": + mute() + break + case "pause": + pause() + break } } function setMenuModel() { - menuModel.clear(); + menuModel.clear() var menuActions = { - start: {name:qsTr('Start'), action:'start'}, - pause: {name:qsTr('Pause'), action:'pause'}, - reset: {name:qsTr('Reset'), action:'reset'}, - mute: {name:qsTr('Mute'), action:'mute'} + start: {name:qsTr("Start"), action:"start"}, + pause: {name:qsTr("Pause"), action:"pause"}, + reset: {name:qsTr("Reset"), action:"reset"}, + mute: {name:qsTr("Mute"), action:"mute"} } if(isRunning) { - menuModel.append(menuActions.pause); - menuModel.append(menuActions.reset); + menuModel.append(menuActions.pause) + menuModel.append(menuActions.reset) } else if(!isRunning && (minutes > 0 || seconds > 0)) { - menuModel.append(menuActions.start); - menuModel.append(menuActions.reset); + menuModel.append(menuActions.start) + menuModel.append(menuActions.reset) } else if(minutes > 0 || seconds > 0) { - menuModel.append(menuActions.reset); + menuModel.append(menuActions.reset) } else if(alarm.playing) { - menuModel.append(menuActions.mute); + menuModel.append(menuActions.mute) } } - } - - diff --git a/qml/pages/TimersDialog.qml b/qml/pages/TimersDialog.qml index e0507c9..e6d133a 100644 --- a/qml/pages/TimersDialog.qml +++ b/qml/pages/TimersDialog.qml @@ -34,24 +34,20 @@ import "../components" Dialog { id: timersDialog; - allowedOrientations: Orientation.Portrait | Orientation.Landscape; + allowedOrientations: Orientation.Portrait | Orientation.Landscape DialogHeader { - id: header; + id: header dialog: timersDialog title: qsTr("Timers") } SilicaListView { - id: timersList; + id: timersList // TODO: Hide when list is outta sight header: SectionHeader { - //visible: text: qsTr("Max value is '59:59'") - onYChanged: { - console.log("onYChanged:", y) - } } footer: SectionHeader { @@ -62,70 +58,70 @@ Dialog { PushUpMenu { id: menu MenuItem { - text: qsTr('Add timer'); + text: qsTr("Add timer") onClicked: { console.log("Adding timer") timersModel.append({ - name: 'New timer', + name: qsTr("New timer"), minutes: 0, seconds: 0 - }); - timersList.positionViewAtEnd(); + }) + timersList.positionViewAtEnd() } } } - model: timersModel; + model: timersModel anchors { horizontalCenter: header.horizontalCenter - leftMargin: Theme.paddingLarge; - rightMargin: Theme.paddingLarge; + leftMargin: Theme.paddingLarge + rightMargin: Theme.paddingLarge } - width: Screen.width; - y: header.height + Theme.paddingMedium; - contentHeight: timersModel.count * Theme.itemSizeMedium; - height: parent.height - (header.height + Theme.paddingMedium); + width: Screen.width + y: header.height + Theme.paddingMedium + contentHeight: timersModel.count * Theme.itemSizeMedium + height: parent.height - (header.height + Theme.paddingMedium) delegate: ListItem { - id: timerItem; - contentHeight: Theme.itemSizeSmall; + id: timerItem + contentHeight: Theme.itemSizeSmall ListView.onRemove: animateRemoval(timerItem) function remove() { - remorseAction(qsTr('Deleting'), function() { - timersList.model.remove(index); + remorseAction(qsTr("Deleting"), function() { + timersList.model.remove(index) }); } Item { TextField { - id: name; - placeholderText: qsTr('Timer name'); - text: model.name; - width: font.pixelSize * 8; + id: name + placeholderText: qsTr("Timer name") + text: model.name + width: font.pixelSize * 8 RegExpValidator { regExp: /(\w{1,10}\b)/g } EnterKey.enabled: text.length > 0 EnterKey.iconSource: "image://theme/icon-m-enter-next" EnterKey.onClicked: minutes.focus = true onFocusChanged: { if(text.length > 0) { - timersModel.setProperty(index, 'name', text); + timersModel.setProperty(index, "name", text) } } } TimeField { id: minutes - anchors.left: name.right; + anchors.left: name.right timeType: "minutes" - text: model.minutes >= 10 ? model.minutes : '0' + String(model.minutes); - placeholderText: qsTr('Minutes'); + text: model.minutes >= 10 ? model.minutes : '0' + String(model.minutes) + placeholderText: qsTr("Minutes") errorHighlight: !validateTime(index, text, "minutes") EnterKey.enabled: validateTime(index, text, "minutes") EnterKey.onClicked: seconds.focus = true onFocusChanged: { if(validateTime(index, text, timeType)) { - timersModel.setProperty(index, timeType, formatTime(text)); + timersModel.setProperty(index, timeType, formatTime(text)) } else { // Grab the value from the model text = formatTime(timersModel.get(index)[timeType]) @@ -134,24 +130,24 @@ Dialog { } Label { - id: separator; - anchors.left: minutes.right; - text: ':'; - color: minutes.color; + id: separator + anchors.left: minutes.right + text: ':' + color: minutes.color } TimeField { id: seconds - anchors.left: separator.right; + anchors.left: separator.right timeType: "seconds" - text: model.seconds >= 10 ? String(model.seconds) : '0' + String(model.seconds); - placeholderText: qsTr('Seconds'); + text: model.seconds >= 10 ? String(model.seconds) : '0' + String(model.seconds) + placeholderText: qsTr("Seconds") errorHighlight: !validateTime(index, text, timeType) EnterKey.enabled: validateTime(index, text, timeType) EnterKey.onClicked: minutes.focus = true onFocusChanged: { if(validateTime(index, text, timeType)) { - timersModel.setProperty(index, timeType, formatTime(text)); + timersModel.setProperty(index, timeType, formatTime(text)) } else { // Grab the value from the model text = formatTime(timersModel.get(index)[timeType]) @@ -160,41 +156,24 @@ Dialog { } IconButton { - anchors.left: seconds.right; - icon.source: 'image://theme/icon-m-delete'; - onClicked: remove(); + anchors.left: seconds.right + icon.source: "image://theme/icon-m-delete" + onClicked: remove() } } } VerticalScrollDecorator { - flickable: timersList; + flickable: timersList } } - onDone: { - result === DialogResult.Accepted ? save() : reload(); - } - - function formatTime(text) { - var t = parseInt(text), newText - - // If the delegate hasn't instantiated yet - if(t === NaN) { - return "00" - } - - // I'd like to do this in a READABLE one-liner - // Make sure that time is not more than 59 mins. and 59 secs - newText = t < 60 ? String(t) : "59" - // Format time '0' => '00', '9' => '09' etc. - newText = t >= 10 ? String(t) : "0" + String(t); - return newText - } + onAccepted: saveTimers() + onRejected: reloadTimers() /* - * idx: int: Model index - * timeText: String: The actual text in the TextField - * minsec: String: Whether it's a "minutes" or "seconds" field + * in idx: Model index + * string timeText: The actual text in the TextField + * string minsec: Whether it's a "minutes" or "seconds" field */ function validateTime(idx, timeText, minsec) { var minutes, seconds, item = timersModel.get(idx) diff --git a/src/harbour-kitchentimer.cpp b/src/harbour-kitchentimer.cpp index bf40295..fff7424 100644 --- a/src/harbour-kitchentimer.cpp +++ b/src/harbour-kitchentimer.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include "qmlsettings.h" #include "display.h" @@ -58,6 +59,7 @@ int main(int argc, char *argv[]) // To display the view, call "show()" (will show fullscreen on device). //return SailfishApp::main(argc, argv); + QLoggingCategory::setFilterRules(QStringLiteral("qt.qml.binding.removal.info=true")); //QGuiApplication* app = SailfishApp::application(argc, argv); QScopedPointer app(SailfishApp::application(argc, argv));