mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-07-21 11:14:38 +02:00
Add:Android auto sleep timer #260
This commit is contained in:
parent
479de5f067
commit
31531197a2
13 changed files with 346 additions and 43 deletions
|
@ -98,7 +98,11 @@ data class DeviceSettings(
|
||||||
var disableShakeToResetSleepTimer:Boolean,
|
var disableShakeToResetSleepTimer:Boolean,
|
||||||
var shakeSensitivity: ShakeSensitivitySetting,
|
var shakeSensitivity: ShakeSensitivitySetting,
|
||||||
var lockOrientation: LockOrientationSetting,
|
var lockOrientation: LockOrientationSetting,
|
||||||
var hapticFeedback: HapticFeedbackSetting
|
var hapticFeedback: HapticFeedbackSetting,
|
||||||
|
var autoSleepTimer: Boolean,
|
||||||
|
var autoSleepTimerStartTime: String,
|
||||||
|
var autoSleepTimerEndTime: String,
|
||||||
|
var sleepTimerLength: Long // Time in milliseconds
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
// Static method to get default device settings
|
// Static method to get default device settings
|
||||||
|
@ -111,7 +115,11 @@ data class DeviceSettings(
|
||||||
disableShakeToResetSleepTimer = false,
|
disableShakeToResetSleepTimer = false,
|
||||||
shakeSensitivity = ShakeSensitivitySetting.MEDIUM,
|
shakeSensitivity = ShakeSensitivitySetting.MEDIUM,
|
||||||
lockOrientation = LockOrientationSetting.NONE,
|
lockOrientation = LockOrientationSetting.NONE,
|
||||||
hapticFeedback = HapticFeedbackSetting.LIGHT
|
hapticFeedback = HapticFeedbackSetting.LIGHT,
|
||||||
|
autoSleepTimer = false,
|
||||||
|
autoSleepTimerStartTime = "22:00",
|
||||||
|
autoSleepTimerEndTime = "06:00",
|
||||||
|
sleepTimerLength = 900000L // 15 minutes
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,6 +128,14 @@ data class DeviceSettings(
|
||||||
val jumpBackwardsTimeMs get() = jumpBackwardsTime * 1000L
|
val jumpBackwardsTimeMs get() = jumpBackwardsTime * 1000L
|
||||||
@get:JsonIgnore
|
@get:JsonIgnore
|
||||||
val jumpForwardTimeMs get() = jumpForwardTime * 1000L
|
val jumpForwardTimeMs get() = jumpForwardTime * 1000L
|
||||||
|
@get:JsonIgnore
|
||||||
|
val autoSleepTimerStartHour get() = autoSleepTimerStartTime.split(":")[0].toInt()
|
||||||
|
@get:JsonIgnore
|
||||||
|
val autoSleepTimerStartMinute get() = autoSleepTimerStartTime.split(":")[1].toInt()
|
||||||
|
@get:JsonIgnore
|
||||||
|
val autoSleepTimerEndHour get() = autoSleepTimerEndTime.split(":")[0].toInt()
|
||||||
|
@get:JsonIgnore
|
||||||
|
val autoSleepTimerEndMinute get() = autoSleepTimerEndTime.split(":")[1].toInt()
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun getShakeThresholdGravity() : Float { // Used in ShakeDetector
|
fun getShakeThresholdGravity() : Float { // Used in ShakeDetector
|
||||||
|
|
|
@ -14,6 +14,7 @@ import com.audiobookshelf.app.R
|
||||||
import com.audiobookshelf.app.device.DeviceManager
|
import com.audiobookshelf.app.device.DeviceManager
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
|
import com.audiobookshelf.app.player.PLAYMETHOD_LOCAL
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
|
|
@ -84,6 +84,12 @@ class PlaybackSession(
|
||||||
return chapters.find { time >= it.startMs && it.endMs > time}
|
return chapters.find { time >= it.startMs && it.endMs > time}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
fun getCurrentTrackEndTime():Long? {
|
||||||
|
val currentTrack = audioTracks[this.getCurrentTrackIndex()]
|
||||||
|
return currentTrack.startOffsetMs + currentTrack.durationMs
|
||||||
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun getCurrentTrackTimeMs():Long {
|
fun getCurrentTrackTimeMs():Long {
|
||||||
val currentTrack = audioTracks[this.getCurrentTrackIndex()]
|
val currentTrack = audioTracks[this.getCurrentTrackIndex()]
|
||||||
|
|
|
@ -26,6 +26,16 @@ object DeviceManager {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Log.d(tag, "Device Manager Singleton invoked")
|
Log.d(tag, "Device Manager Singleton invoked")
|
||||||
|
|
||||||
|
// Initialize new sleep timer settings and shake sensitivity added in v0.9.61
|
||||||
|
if (deviceData.deviceSettings?.autoSleepTimerStartTime == null || deviceData.deviceSettings?.autoSleepTimerEndTime == null) {
|
||||||
|
deviceData.deviceSettings?.autoSleepTimerStartTime = "22:00"
|
||||||
|
deviceData.deviceSettings?.autoSleepTimerStartTime = "06:00"
|
||||||
|
deviceData.deviceSettings?.sleepTimerLength = 900000L
|
||||||
|
}
|
||||||
|
if (deviceData.deviceSettings?.shakeSensitivity == null) {
|
||||||
|
deviceData.deviceSettings?.shakeSensitivity = ShakeSensitivitySetting.MEDIUM
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBase64Id(id:String):String {
|
fun getBase64Id(id:String):String {
|
||||||
|
|
|
@ -6,12 +6,11 @@ import android.util.Log
|
||||||
import com.audiobookshelf.app.device.DeviceManager
|
import com.audiobookshelf.app.device.DeviceManager
|
||||||
import com.audiobookshelf.app.player.PlayerNotificationService
|
import com.audiobookshelf.app.player.PlayerNotificationService
|
||||||
import com.audiobookshelf.app.player.SLEEP_TIMER_WAKE_UP_EXPIRATION
|
import com.audiobookshelf.app.player.SLEEP_TIMER_WAKE_UP_EXPIRATION
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.concurrent.schedule
|
import kotlin.concurrent.schedule
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
const val SLEEP_EXTENSION_TIME = 900000L // 15m
|
|
||||||
|
|
||||||
class SleepTimerManager constructor(private val playerNotificationService: PlayerNotificationService) {
|
class SleepTimerManager constructor(private val playerNotificationService: PlayerNotificationService) {
|
||||||
private val tag = "SleepTimerManager"
|
private val tag = "SleepTimerManager"
|
||||||
|
|
||||||
|
@ -58,7 +57,7 @@ class SleepTimerManager constructor(private val playerNotificationService: Playe
|
||||||
fun setSleepTimer(time: Long, isChapterTime: Boolean) : Boolean {
|
fun setSleepTimer(time: Long, isChapterTime: Boolean) : Boolean {
|
||||||
Log.d(tag, "Setting Sleep Timer for $time is chapter time $isChapterTime")
|
Log.d(tag, "Setting Sleep Timer for $time is chapter time $isChapterTime")
|
||||||
sleepTimerTask?.cancel()
|
sleepTimerTask?.cancel()
|
||||||
sleepTimerRunning = false
|
sleepTimerRunning = true
|
||||||
sleepTimerFinishedAt = 0L
|
sleepTimerFinishedAt = 0L
|
||||||
sleepTimerElapsed = 0L
|
sleepTimerElapsed = 0L
|
||||||
|
|
||||||
|
@ -88,7 +87,6 @@ class SleepTimerManager constructor(private val playerNotificationService: Playe
|
||||||
|
|
||||||
playerNotificationService.clientEventEmitter?.onSleepTimerSet(getSleepTimerTimeRemainingSeconds())
|
playerNotificationService.clientEventEmitter?.onSleepTimerSet(getSleepTimerTimeRemainingSeconds())
|
||||||
|
|
||||||
sleepTimerRunning = true
|
|
||||||
sleepTimerTask = Timer("SleepTimer", false).schedule(0L, 1000L) {
|
sleepTimerTask = Timer("SleepTimer", false).schedule(0L, 1000L) {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
if (getIsPlaying()) {
|
if (getIsPlaying()) {
|
||||||
|
@ -120,12 +118,6 @@ class SleepTimerManager constructor(private val playerNotificationService: Playe
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called when playing audio and only applies to regular timer
|
|
||||||
fun resetSleepTimer() {
|
|
||||||
if (!sleepTimerRunning || sleepTimerLength <= 0L) return
|
|
||||||
setSleepTimer(sleepTimerLength, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun clearSleepTimer() {
|
private fun clearSleepTimer() {
|
||||||
sleepTimerTask?.cancel()
|
sleepTimerTask?.cancel()
|
||||||
sleepTimerTask = null
|
sleepTimerTask = null
|
||||||
|
@ -168,7 +160,7 @@ class SleepTimerManager constructor(private val playerNotificationService: Playe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkShouldExtendSleepTimer() {
|
fun checkShouldResetSleepTimer() {
|
||||||
if (!sleepTimerRunning) {
|
if (!sleepTimerRunning) {
|
||||||
if (sleepTimerFinishedAt <= 0L) return
|
if (sleepTimerFinishedAt <= 0L) return
|
||||||
|
|
||||||
|
@ -180,15 +172,27 @@ class SleepTimerManager constructor(private val playerNotificationService: Playe
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val newSleepTime = if (sleepTimerLength >= 0) sleepTimerLength else SLEEP_EXTENSION_TIME
|
// Set sleep timer
|
||||||
|
// When sleepTimerLength is 0 then use end of chapter/track time
|
||||||
|
if (sleepTimerLength == 0L) {
|
||||||
|
val currentChapterEndTimeMs = playerNotificationService.getEndTimeOfChapterOrTrack()
|
||||||
|
if (currentChapterEndTimeMs == null) {
|
||||||
|
Log.e(tag, "Checking reset sleep timer to end of chapter/track but there is no current session")
|
||||||
|
} else {
|
||||||
vibrate()
|
vibrate()
|
||||||
setSleepTimer(newSleepTime, false)
|
setSleepTimer(currentChapterEndTimeMs, true)
|
||||||
play()
|
play()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vibrate()
|
||||||
|
setSleepTimer(sleepTimerLength, false)
|
||||||
|
play()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Does not apply to chapter sleep timers and timer must be running for at least 3 seconds
|
// Does not apply to chapter sleep timers and timer must be running for at least 3 seconds
|
||||||
if (sleepTimerEndTime == 0L && sleepTimerElapsed > 3000L) {
|
if (sleepTimerLength > 0L && sleepTimerElapsed > 3000L) {
|
||||||
vibrate()
|
vibrate()
|
||||||
setSleepTimer(sleepTimerLength, false)
|
setSleepTimer(sleepTimerLength, false)
|
||||||
}
|
}
|
||||||
|
@ -200,7 +204,7 @@ class SleepTimerManager constructor(private val playerNotificationService: Playe
|
||||||
Log.d(tag, "Shake to reset sleep timer is disabled")
|
Log.d(tag, "Shake to reset sleep timer is disabled")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
checkShouldExtendSleepTimer()
|
checkShouldResetSleepTimer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,4 +249,53 @@ class SleepTimerManager constructor(private val playerNotificationService: Playe
|
||||||
setVolume(1F)
|
setVolume(1F)
|
||||||
playerNotificationService.clientEventEmitter?.onSleepTimerSet(getSleepTimerTimeRemainingSeconds())
|
playerNotificationService.clientEventEmitter?.onSleepTimerSet(getSleepTimerTimeRemainingSeconds())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun checkAutoSleepTimer() {
|
||||||
|
if (sleepTimerRunning) { // Sleep timer already running
|
||||||
|
return
|
||||||
|
}
|
||||||
|
DeviceManager.deviceData.deviceSettings?.let { deviceSettings ->
|
||||||
|
if (!deviceSettings.autoSleepTimer) return // Check auto sleep timer is enabled
|
||||||
|
|
||||||
|
val startCalendar = Calendar.getInstance()
|
||||||
|
startCalendar.set(Calendar.HOUR_OF_DAY, deviceSettings.autoSleepTimerStartHour)
|
||||||
|
startCalendar.set(Calendar.MINUTE, deviceSettings.autoSleepTimerStartMinute)
|
||||||
|
val endCalendar = Calendar.getInstance()
|
||||||
|
endCalendar.set(Calendar.HOUR_OF_DAY, deviceSettings.autoSleepTimerEndHour)
|
||||||
|
endCalendar.set(Calendar.MINUTE, deviceSettings.autoSleepTimerEndMinute)
|
||||||
|
|
||||||
|
// In cases where end time is earlier then start time then we add a day to end time
|
||||||
|
// e.g. start time 22:00 and end time 06:00. End time will be treated as 6am the next day.
|
||||||
|
// e.g. start time 08:00 and end time 22:00. Start and end time will be the same day.
|
||||||
|
if (endCalendar.before(startCalendar)) {
|
||||||
|
endCalendar.add(Calendar.DAY_OF_MONTH, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentCalendar = Calendar.getInstance()
|
||||||
|
val currentHour = SimpleDateFormat("HH:mm", Locale.getDefault()).format(currentCalendar.time)
|
||||||
|
if (currentCalendar.after(startCalendar) && currentCalendar.before(endCalendar)) {
|
||||||
|
Log.i(tag, "Current hour $currentHour is between ${deviceSettings.autoSleepTimerStartTime} and ${deviceSettings.autoSleepTimerEndTime} - starting sleep timer")
|
||||||
|
|
||||||
|
// Set sleep timer
|
||||||
|
// When sleepTimerLength is 0 then use end of chapter/track time
|
||||||
|
if (deviceSettings.sleepTimerLength == 0L) {
|
||||||
|
val currentChapterEndTimeMs = playerNotificationService.getEndTimeOfChapterOrTrack()
|
||||||
|
if (currentChapterEndTimeMs == null) {
|
||||||
|
Log.e(tag, "Setting auto sleep timer to end of chapter/track but there is no current session")
|
||||||
|
} else {
|
||||||
|
setSleepTimer(currentChapterEndTimeMs, true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setSleepTimer(deviceSettings.sleepTimerLength, false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d(tag, "Current hour $currentHour is NOT between ${deviceSettings.autoSleepTimerStartTime} and ${deviceSettings.autoSleepTimerEndTime}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleMediaPlayEvent() {
|
||||||
|
checkShouldResetSleepTimer()
|
||||||
|
checkAutoSleepTimer()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,6 +68,9 @@ class MediaProgressSyncer(val playerNotificationService:PlayerNotificationServic
|
||||||
listeningTimerTask = Timer("ListeningTimer", false).schedule(15000L, 15000L) {
|
listeningTimerTask = Timer("ListeningTimer", false).schedule(15000L, 15000L) {
|
||||||
Handler(Looper.getMainLooper()).post() {
|
Handler(Looper.getMainLooper()).post() {
|
||||||
if (playerNotificationService.currentPlayer.isPlaying) {
|
if (playerNotificationService.currentPlayer.isPlaying) {
|
||||||
|
// Set auto sleep timer if enabled and within start/end time
|
||||||
|
playerNotificationService.sleepTimerManager.checkAutoSleepTimer()
|
||||||
|
|
||||||
// Only sync with server on unmetered connection every 15s OR sync with server if last sync time is >= 60s
|
// Only sync with server on unmetered connection every 15s OR sync with server if last sync time is >= 60s
|
||||||
val shouldSyncServer = PlayerNotificationService.isUnmeteredNetwork || System.currentTimeMillis() - lastSyncTime >= METERED_CONNECTION_SYNC_INTERVAL
|
val shouldSyncServer = PlayerNotificationService.isUnmeteredNetwork || System.currentTimeMillis() - lastSyncTime >= METERED_CONNECTION_SYNC_INTERVAL
|
||||||
|
|
||||||
|
|
|
@ -169,7 +169,6 @@ class MediaSessionCallback(var playerNotificationService:PlayerNotificationServi
|
||||||
Log.d(tag, "handleCallMediaButton: Media Play")
|
Log.d(tag, "handleCallMediaButton: Media Play")
|
||||||
if (0 == mediaButtonClickCount) {
|
if (0 == mediaButtonClickCount) {
|
||||||
playerNotificationService.play()
|
playerNotificationService.play()
|
||||||
playerNotificationService.sleepTimerManager.checkShouldExtendSleepTimer()
|
|
||||||
}
|
}
|
||||||
handleMediaButtonClickCount()
|
handleMediaButtonClickCount()
|
||||||
}
|
}
|
||||||
|
@ -201,7 +200,6 @@ class MediaSessionCallback(var playerNotificationService:PlayerNotificationServi
|
||||||
} else {
|
} else {
|
||||||
if (0 == mediaButtonClickCount) {
|
if (0 == mediaButtonClickCount) {
|
||||||
playerNotificationService.play()
|
playerNotificationService.play()
|
||||||
playerNotificationService.sleepTimerManager.checkShouldExtendSleepTimer()
|
|
||||||
}
|
}
|
||||||
handleMediaButtonClickCount()
|
handleMediaButtonClickCount()
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,7 +92,9 @@ class PlayerListener(var playerNotificationService:PlayerNotificationService) :
|
||||||
|
|
||||||
// Start/stop progress sync interval
|
// Start/stop progress sync interval
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
playerNotificationService.sleepTimerManager.resetSleepTimer() // Reset sleep timer if running and not a chapter timer
|
// Handles auto-starting sleep timer and resetting sleep timer
|
||||||
|
playerNotificationService.sleepTimerManager.handleMediaPlayEvent()
|
||||||
|
|
||||||
player.volume = 1F // Volume on sleep timer might have decreased this
|
player.volume = 1F // Volume on sleep timer might have decreased this
|
||||||
val playbackSession: PlaybackSession? = playerNotificationService.mediaProgressSyncer.currentPlaybackSession ?: playerNotificationService.currentPlaybackSession
|
val playbackSession: PlaybackSession? = playerNotificationService.mediaProgressSyncer.currentPlaybackSession ?: playerNotificationService.currentPlaybackSession
|
||||||
playbackSession?.let { playerNotificationService.mediaProgressSyncer.play(it) }
|
playbackSession?.let { playerNotificationService.mediaProgressSyncer.play(it) }
|
||||||
|
|
|
@ -644,6 +644,10 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
return currentPlaybackSession?.getChapterForTime(this.getCurrentTime())
|
return currentPlaybackSession?.getChapterForTime(this.getCurrentTime())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getEndTimeOfChapterOrTrack():Long? {
|
||||||
|
return getCurrentBookChapter()?.endMs ?: currentPlaybackSession?.getCurrentTrackEndTime()
|
||||||
|
}
|
||||||
|
|
||||||
// Called from PlayerListener play event
|
// Called from PlayerListener play event
|
||||||
// check with server if progress has updated since last play and sync progress update
|
// check with server if progress has updated since last play and sync progress update
|
||||||
fun checkCurrentSessionProgress(seekBackTime:Long):Boolean {
|
fun checkCurrentSessionProgress(seekBackTime:Long):Boolean {
|
||||||
|
|
100
components/modals/SleepTimerLengthModal.vue
Normal file
100
components/modals/SleepTimerLengthModal.vue
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
<template>
|
||||||
|
<modals-modal v-model="show" :width="200" height="100%">
|
||||||
|
<template #outer>
|
||||||
|
<div class="absolute top-5 left-4 z-40">
|
||||||
|
<p class="text-white text-2xl truncate">Sleep Timer</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="w-full h-full overflow-hidden absolute top-0 left-0 flex items-center justify-center"
|
||||||
|
@click="
|
||||||
|
show = false
|
||||||
|
manualTimerModal = false
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div class="w-full overflow-x-hidden overflow-y-auto bg-primary rounded-lg border border-white border-opacity-20" style="max-height: 75%" @click.stop>
|
||||||
|
<div v-if="manualTimerModal" class="p-4">
|
||||||
|
<div class="flex mb-4" @click="manualTimerModal = false">
|
||||||
|
<span class="material-icons text-3xl">arrow_back</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex my-2 justify-between">
|
||||||
|
<ui-btn @click="decreaseManualTimeout" class="w-9 h-9" :padding-x="0" small style="max-width: 36px"><span class="material-icons">remove</span></ui-btn>
|
||||||
|
<p class="text-2xl font-mono text-center">{{ manualTimeoutMin }} min</p>
|
||||||
|
<ui-btn @click="increaseManualTimeout" class="w-9 h-9" :padding-x="0" small style="max-width: 36px"><span class="material-icons">add</span></ui-btn>
|
||||||
|
</div>
|
||||||
|
<ui-btn @click="clickedOption(manualTimeoutMin)" class="w-full">Set Timer</ui-btn>
|
||||||
|
</div>
|
||||||
|
<ul v-else class="h-full w-full" role="listbox" aria-labelledby="listbox-label">
|
||||||
|
<template v-for="timeout in timeouts">
|
||||||
|
<li :key="timeout" class="text-gray-50 select-none relative py-4 cursor-pointer hover:bg-black-400" role="option" @click="clickedOption(timeout)">
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<span class="font-normal block truncate text-lg">{{ timeout }} min</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
<li class="text-gray-50 select-none relative py-4 cursor-pointer hover:bg-black-400" role="option" @click="clickedChapterOption">
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<span class="font-normal block truncate text-lg text-center">End of Chapter</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="text-gray-50 select-none relative py-4 cursor-pointer hover:bg-black-400" role="option" @click="manualTimerModal = true">
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<span class="font-normal block truncate text-lg text-center">Custom time</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</modals-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
value: Boolean
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
manualTimerModal: null,
|
||||||
|
manualTimeoutMin: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
show: {
|
||||||
|
get() {
|
||||||
|
return this.value
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
this.$emit('input', val)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
timeouts() {
|
||||||
|
return [5, 10, 15, 30, 45, 60, 90]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async clickedChapterOption() {
|
||||||
|
await this.$hapticsImpact()
|
||||||
|
this.show = false
|
||||||
|
this.$nextTick(() => this.$emit('change', 0))
|
||||||
|
},
|
||||||
|
async clickedOption(timeoutMin) {
|
||||||
|
await this.$hapticsImpact()
|
||||||
|
const timeout = timeoutMin * 1000 * 60
|
||||||
|
this.show = false
|
||||||
|
this.manualTimerModal = false
|
||||||
|
this.$nextTick(() => this.$emit('change', timeout))
|
||||||
|
},
|
||||||
|
async increaseManualTimeout() {
|
||||||
|
await this.$hapticsImpact()
|
||||||
|
this.manualTimeoutMin++
|
||||||
|
},
|
||||||
|
async decreaseManualTimeout() {
|
||||||
|
await this.$hapticsImpact()
|
||||||
|
if (this.manualTimeoutMin > 1) this.manualTimeoutMin--
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -40,7 +40,7 @@
|
||||||
</li>
|
</li>
|
||||||
<li class="text-gray-50 select-none relative py-4 cursor-pointer hover:bg-black-400" role="option" @click="manualTimerModal = true">
|
<li class="text-gray-50 select-none relative py-4 cursor-pointer hover:bg-black-400" role="option" @click="manualTimerModal = true">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<span class="font-normal block truncate text-lg text-center">Manual sleep timer</span>
|
<span class="font-normal block truncate text-lg text-center">Custom time</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -96,7 +96,7 @@ export default {
|
||||||
},
|
},
|
||||||
async clickedOption(timeoutMin) {
|
async clickedOption(timeoutMin) {
|
||||||
await this.$hapticsImpact()
|
await this.$hapticsImpact()
|
||||||
var timeout = timeoutMin * 1000 * 60
|
const timeout = timeoutMin * 1000 * 60
|
||||||
this.show = false
|
this.show = false
|
||||||
this.manualTimerModal = false
|
this.manualTimerModal = false
|
||||||
this.$nextTick(() => this.$emit('change', { time: timeout, isChapterTime: false }))
|
this.$nextTick(() => this.$emit('change', { time: timeout, isChapterTime: false }))
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<input v-model="input" ref="input" autofocus :type="type" :disabled="disabled" autocorrect="off" autocapitalize="none" autocomplete="off" :placeholder="placeholder" class="py-2 w-full outline-none bg-primary" :class="inputClass" @keyup="keyup" />
|
<input v-model="input" ref="input" autofocus :type="type" :disabled="disabled" :readonly="readonly" autocorrect="off" autocapitalize="none" autocomplete="off" :placeholder="placeholder" class="py-2 w-full outline-none bg-primary" :class="inputClass" @keyup="keyup" />
|
||||||
<div v-if="prependIcon" class="absolute top-0 left-0 h-full px-2 flex items-center justify-center">
|
<div v-if="prependIcon" class="absolute top-0 left-0 h-full px-2 flex items-center justify-center">
|
||||||
<span class="material-icons text-lg">{{ prependIcon }}</span>
|
<span class="material-icons text-lg">{{ prependIcon }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="clearable && input" class="absolute top-0 right-0 h-full px-2 flex items-center justify-center" @click.stop="clear">
|
<div v-if="clearable && input" class="absolute top-0 right-0 h-full px-2 flex items-center justify-center" @click.stop="clear">
|
||||||
<span class="material-icons text-lg">close</span>
|
<span class="material-icons text-lg">close</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else-if="!clearable && appendIcon" class="absolute top-0 right-0 h-full px-2 flex items-center justify-center">
|
||||||
|
<span class="material-icons text-lg">{{ appendIcon }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -17,6 +20,7 @@ export default {
|
||||||
placeholder: String,
|
placeholder: String,
|
||||||
type: String,
|
type: String,
|
||||||
disabled: Boolean,
|
disabled: Boolean,
|
||||||
|
readonly: Boolean,
|
||||||
borderless: Boolean,
|
borderless: Boolean,
|
||||||
bg: {
|
bg: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -30,6 +34,10 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
|
appendIcon: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
clearable: Boolean
|
clearable: Boolean
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
@ -75,3 +83,9 @@ export default {
|
||||||
mounted() {}
|
mounted() {}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
input[type='time']::-webkit-calendar-picker-indicator {
|
||||||
|
filter: invert(100%);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="w-full h-full px-8 pt-8 pb-48 overflow-y-auto">
|
<div class="w-full h-full px-8 py-8 overflow-y-auto">
|
||||||
<!-- Display settings -->
|
<!-- Display settings -->
|
||||||
<p class="uppercase text-xs font-semibold text-gray-300 mb-2">User Interface Settings</p>
|
<p class="uppercase text-xs font-semibold text-gray-300 mb-2">User Interface Settings</p>
|
||||||
<div class="flex items-center py-3" @click="toggleEnableAltView">
|
<div class="flex items-center py-3" @click="toggleEnableAltView">
|
||||||
|
@ -15,8 +15,10 @@
|
||||||
<p class="pl-4">Lock orientation</p>
|
<p class="pl-4">Lock orientation</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="py-3 flex items-center">
|
<div class="py-3 flex items-center">
|
||||||
<p class="pr-4">Haptic feedback</p>
|
<p class="pr-4 w-36">Haptic feedback</p>
|
||||||
<ui-dropdown v-model="settings.hapticFeedback" :items="hapticFeedbackItems" style="max-width: 105px" @input="hapticFeedbackUpdated" />
|
<div @click.stop="showHapticFeedbackOptions">
|
||||||
|
<ui-text-input :value="hapticFeedbackOption" readonly append-icon="expand_more" style="max-width: 145px" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Playback settings -->
|
<!-- Playback settings -->
|
||||||
|
@ -41,19 +43,48 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Sleep timer settings -->
|
<!-- Sleep timer settings -->
|
||||||
<p v-if="!isiOS" class="uppercase text-xs font-semibold text-gray-300 mb-2 mt-6">Sleep Timer Settings</p>
|
<template v-if="!isiOS">
|
||||||
<div v-if="!isiOS" class="flex items-center py-3" @click="toggleDisableShakeToResetSleepTimer">
|
<p class="uppercase text-xs font-semibold text-gray-300 mb-2 mt-6">Sleep Timer Settings</p>
|
||||||
|
<div class="flex items-center py-3" @click="toggleDisableShakeToResetSleepTimer">
|
||||||
<div class="w-10 flex justify-center">
|
<div class="w-10 flex justify-center">
|
||||||
<ui-toggle-switch v-model="settings.disableShakeToResetSleepTimer" @input="saveSettings" />
|
<ui-toggle-switch v-model="settings.disableShakeToResetSleepTimer" @input="saveSettings" />
|
||||||
</div>
|
</div>
|
||||||
<p class="pl-4">Disable shake to reset</p>
|
<p class="pl-4">Disable shake to reset</p>
|
||||||
<span class="material-icons-outlined ml-2" @click.stop="showInfo('disableShakeToResetSleepTimer')">info</span>
|
<span class="material-icons-outlined ml-2" @click.stop="showInfo('disableShakeToResetSleepTimer')">info</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!isiOS && !settings.disableShakeToResetSleepTimer" class="py-3 flex items-center">
|
<div v-if="!settings.disableShakeToResetSleepTimer" class="py-3 flex items-center">
|
||||||
<p class="pr-4">Shake Sensitivity</p>
|
<p class="pr-4 w-36">Shake Sensitivity</p>
|
||||||
<ui-dropdown v-model="settings.shakeSensitivity" :items="shakeSensitivityItems" style="max-width: 125px" @input="sensitivityUpdated" />
|
<div @click.stop="showShakeSensitivityOptions">
|
||||||
|
<ui-text-input :value="shakeSensitivityOption" readonly append-icon="expand_more" style="width: 145px; max-width: 145px" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center py-3" @click="toggleAutoSleepTimer">
|
||||||
|
<div class="w-10 flex justify-center">
|
||||||
|
<ui-toggle-switch v-model="settings.autoSleepTimer" @input="saveSettings" />
|
||||||
|
</div>
|
||||||
|
<p class="pl-4">Auto Sleep Timer</p>
|
||||||
|
<span class="material-icons-outlined ml-2" @click.stop="showInfo('autoSleepTimer')">info</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<!-- Auto Sleep timer settings -->
|
||||||
|
<div v-if="settings.autoSleepTimer" class="py-3 flex items-center">
|
||||||
|
<p class="pr-4 w-36">Start Time</p>
|
||||||
|
<ui-text-input type="time" v-model="settings.autoSleepTimerStartTime" style="width: 145px; max-width: 145px" @input="autoSleepTimerTimeUpdated" />
|
||||||
|
</div>
|
||||||
|
<div v-if="settings.autoSleepTimer" class="py-3 flex items-center">
|
||||||
|
<p class="pr-4 w-36">End Time</p>
|
||||||
|
<ui-text-input type="time" v-model="settings.autoSleepTimerEndTime" style="width: 145px; max-width: 145px" @input="autoSleepTimerTimeUpdated" />
|
||||||
|
</div>
|
||||||
|
<div v-if="settings.autoSleepTimer" class="py-3 flex items-center">
|
||||||
|
<p class="pr-4 w-36">Sleep Timer</p>
|
||||||
|
<div @click.stop="showSleepTimerOptions">
|
||||||
|
<ui-text-input :value="sleepTimerLengthOption" readonly append-icon="expand_more" style="width: 145px; max-width: 145px" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<modals-dialog v-model="showMoreMenuDialog" :items="moreMenuItems" @action="clickMenuAction" />
|
||||||
|
<modals-sleep-timer-length-modal v-model="showSleepTimerLengthModal" @change="sleepTimerLengthModalSelection" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -63,6 +94,9 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
deviceData: null,
|
deviceData: null,
|
||||||
|
showMoreMenuDialog: false,
|
||||||
|
showSleepTimerLengthModal: false,
|
||||||
|
moreMenuSetting: '',
|
||||||
settings: {
|
settings: {
|
||||||
disableAutoRewind: false,
|
disableAutoRewind: false,
|
||||||
enableAltView: false,
|
enableAltView: false,
|
||||||
|
@ -71,15 +105,23 @@ export default {
|
||||||
disableShakeToResetSleepTimer: false,
|
disableShakeToResetSleepTimer: false,
|
||||||
shakeSensitivity: 'MEDIUM',
|
shakeSensitivity: 'MEDIUM',
|
||||||
lockOrientation: 0,
|
lockOrientation: 0,
|
||||||
hapticFeedback: 'LIGHT'
|
hapticFeedback: 'LIGHT',
|
||||||
|
autoSleepTimer: false,
|
||||||
|
autoSleepTimerStartTime: '22:00',
|
||||||
|
autoSleepTimerEndTime: '06:00',
|
||||||
|
sleepTimerLength: 900000 // 15 minutes
|
||||||
},
|
},
|
||||||
|
lockCurrentOrientation: false,
|
||||||
settingInfo: {
|
settingInfo: {
|
||||||
disableShakeToResetSleepTimer: {
|
disableShakeToResetSleepTimer: {
|
||||||
name: 'Disable shake to reset sleep timer',
|
name: 'Disable shake to reset sleep timer',
|
||||||
message: 'Shaking your device while the timer is running OR within 2 minutes of the timer expiring will reset the sleep timer. Enable this setting to disable shake to reset.'
|
message: 'Shaking your device while the timer is running OR within 2 minutes of the timer expiring will reset the sleep timer. Enable this setting to disable shake to reset.'
|
||||||
|
},
|
||||||
|
autoSleepTimer: {
|
||||||
|
name: 'Auto Sleep Timer',
|
||||||
|
message: 'When playing media between the specified start and end times a sleep timer will automatically start.'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
lockCurrentOrientation: false,
|
|
||||||
hapticFeedbackItems: [
|
hapticFeedbackItems: [
|
||||||
{
|
{
|
||||||
text: 'Off',
|
text: 'Off',
|
||||||
|
@ -145,10 +187,55 @@ export default {
|
||||||
currentJumpBackwardsTimeIndex() {
|
currentJumpBackwardsTimeIndex() {
|
||||||
var index = this.jumpBackwardsItems.findIndex((jfi) => jfi.value === this.settings.jumpBackwardsTime)
|
var index = this.jumpBackwardsItems.findIndex((jfi) => jfi.value === this.settings.jumpBackwardsTime)
|
||||||
return index >= 0 ? index : 1
|
return index >= 0 ? index : 1
|
||||||
|
},
|
||||||
|
shakeSensitivityOption() {
|
||||||
|
const item = this.shakeSensitivityItems.find((i) => i.value === this.settings.shakeSensitivity)
|
||||||
|
return item ? item.text : 'Error'
|
||||||
|
},
|
||||||
|
hapticFeedbackOption() {
|
||||||
|
const item = this.hapticFeedbackItems.find((i) => i.value === this.settings.hapticFeedback)
|
||||||
|
return item ? item.text : 'Error'
|
||||||
|
},
|
||||||
|
sleepTimerLengthOption() {
|
||||||
|
if (!this.settings.sleepTimerLength) return 'End of Chapter'
|
||||||
|
const minutes = Number(this.settings.sleepTimerLength) / 1000 / 60
|
||||||
|
return `${minutes} min`
|
||||||
|
},
|
||||||
|
moreMenuItems() {
|
||||||
|
if (this.moreMenuSetting === 'shakeSensitivity') return this.shakeSensitivityItems
|
||||||
|
else if (this.moreMenuSetting === 'hapticFeedback') return this.hapticFeedbackItems
|
||||||
|
return []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
sensitivityUpdated(val) {
|
sleepTimerLengthModalSelection(value) {
|
||||||
|
this.settings.sleepTimerLength = value
|
||||||
|
this.saveSettings()
|
||||||
|
},
|
||||||
|
showSleepTimerOptions() {
|
||||||
|
this.showSleepTimerLengthModal = true
|
||||||
|
},
|
||||||
|
showHapticFeedbackOptions() {
|
||||||
|
this.moreMenuSetting = 'hapticFeedback'
|
||||||
|
this.showMoreMenuDialog = true
|
||||||
|
},
|
||||||
|
showShakeSensitivityOptions() {
|
||||||
|
this.moreMenuSetting = 'shakeSensitivity'
|
||||||
|
this.showMoreMenuDialog = true
|
||||||
|
},
|
||||||
|
clickMenuAction(action) {
|
||||||
|
this.showMoreMenuDialog = false
|
||||||
|
if (this.moreMenuSetting === 'shakeSensitivity') {
|
||||||
|
this.settings.shakeSensitivity = action
|
||||||
|
this.saveSettings()
|
||||||
|
} else if (this.moreMenuSetting === 'hapticFeedback') {
|
||||||
|
this.settings.hapticFeedback = action
|
||||||
|
this.hapticFeedbackUpdated(action)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
autoSleepTimerTimeUpdated(val) {
|
||||||
|
console.log('[settings] Auto sleep timer time=', val)
|
||||||
|
if (!val) return // invalid times return falsy
|
||||||
this.saveSettings()
|
this.saveSettings()
|
||||||
},
|
},
|
||||||
hapticFeedbackUpdated(val) {
|
hapticFeedbackUpdated(val) {
|
||||||
|
@ -163,6 +250,10 @@ export default {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
toggleAutoSleepTimer() {
|
||||||
|
this.settings.autoSleepTimer = !this.settings.autoSleepTimer
|
||||||
|
this.saveSettings()
|
||||||
|
},
|
||||||
toggleDisableShakeToResetSleepTimer() {
|
toggleDisableShakeToResetSleepTimer() {
|
||||||
this.settings.disableShakeToResetSleepTimer = !this.settings.disableShakeToResetSleepTimer
|
this.settings.disableShakeToResetSleepTimer = !this.settings.disableShakeToResetSleepTimer
|
||||||
this.saveSettings()
|
this.saveSettings()
|
||||||
|
@ -220,11 +311,16 @@ export default {
|
||||||
this.settings.enableAltView = !!deviceSettings.enableAltView
|
this.settings.enableAltView = !!deviceSettings.enableAltView
|
||||||
this.settings.jumpForwardTime = deviceSettings.jumpForwardTime || 10
|
this.settings.jumpForwardTime = deviceSettings.jumpForwardTime || 10
|
||||||
this.settings.jumpBackwardsTime = deviceSettings.jumpBackwardsTime || 10
|
this.settings.jumpBackwardsTime = deviceSettings.jumpBackwardsTime || 10
|
||||||
this.settings.disableShakeToResetSleepTimer = !!deviceSettings.disableShakeToResetSleepTimer
|
|
||||||
this.settings.shakeSensitivity = deviceSettings.shakeSensitivity || 'MEDIUM'
|
|
||||||
this.settings.lockOrientation = deviceSettings.lockOrientation || 'NONE'
|
this.settings.lockOrientation = deviceSettings.lockOrientation || 'NONE'
|
||||||
this.lockCurrentOrientation = this.settings.lockOrientation !== 'NONE'
|
this.lockCurrentOrientation = this.settings.lockOrientation !== 'NONE'
|
||||||
this.settings.hapticFeedback = deviceSettings.hapticFeedback || 'LIGHT'
|
this.settings.hapticFeedback = deviceSettings.hapticFeedback || 'LIGHT'
|
||||||
|
|
||||||
|
this.settings.disableShakeToResetSleepTimer = !!deviceSettings.disableShakeToResetSleepTimer
|
||||||
|
this.settings.shakeSensitivity = deviceSettings.shakeSensitivity || 'MEDIUM'
|
||||||
|
this.settings.autoSleepTimer = !!deviceSettings.autoSleepTimer
|
||||||
|
this.settings.autoSleepTimerStartTime = deviceSettings.autoSleepTimerStartTime || '22:00'
|
||||||
|
this.settings.autoSleepTimerEndTime = deviceSettings.autoSleepTimerEndTime || '06:00'
|
||||||
|
this.settings.sleepTimerLength = !isNaN(deviceSettings.sleepTimerLength) ? deviceSettings.sleepTimerLength : 900000 // 15 minutes
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue