mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-29 00:10:32 +05:30
Merge pull request #5204 from Bnyro/sleep-timer
feat: new sleep timer (integrated into the player UI)
This commit is contained in:
commit
61d85033ee
@ -19,8 +19,6 @@ object PreferenceKeys {
|
|||||||
const val LANGUAGE = "language"
|
const val LANGUAGE = "language"
|
||||||
const val REGION = "region"
|
const val REGION = "region"
|
||||||
const val ORIENTATION = "orientation"
|
const val ORIENTATION = "orientation"
|
||||||
const val SLEEP_TIMER = "sleep_timer_toggle"
|
|
||||||
const val SLEEP_TIMER_DELAY = "sleep_timer_delay"
|
|
||||||
const val SAVE_FEED = "save_feed"
|
const val SAVE_FEED = "save_feed"
|
||||||
const val NAVBAR_ITEMS = "navbar_items"
|
const val NAVBAR_ITEMS = "navbar_items"
|
||||||
const val START_FRAGMENT = "start_fragment"
|
const val START_FRAGMENT = "start_fragment"
|
||||||
|
@ -42,7 +42,6 @@ import com.github.libretube.ui.fragments.PlayerFragment
|
|||||||
import com.github.libretube.ui.models.PlayerViewModel
|
import com.github.libretube.ui.models.PlayerViewModel
|
||||||
import com.github.libretube.ui.models.SearchViewModel
|
import com.github.libretube.ui.models.SearchViewModel
|
||||||
import com.github.libretube.ui.models.SubscriptionsViewModel
|
import com.github.libretube.ui.models.SubscriptionsViewModel
|
||||||
import com.github.libretube.ui.tools.SleepTimer
|
|
||||||
import com.google.android.material.elevation.SurfaceColors
|
import com.google.android.material.elevation.SurfaceColors
|
||||||
|
|
||||||
class MainActivity : BaseActivity() {
|
class MainActivity : BaseActivity() {
|
||||||
@ -138,8 +137,6 @@ class MainActivity : BaseActivity() {
|
|||||||
ErrorDialog().show(supportFragmentManager, null)
|
ErrorDialog().show(supportFragmentManager, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
SleepTimer.setup(this)
|
|
||||||
|
|
||||||
setupSubscriptionsBadge()
|
setupSubscriptionsBadge()
|
||||||
|
|
||||||
// new way of handling back presses
|
// new way of handling back presses
|
||||||
|
@ -7,4 +7,5 @@ interface PlayerOptions {
|
|||||||
fun onResizeModeClicked()
|
fun onResizeModeClicked()
|
||||||
|
|
||||||
fun onRepeatModeClicked()
|
fun onRepeatModeClicked()
|
||||||
|
fun onSleepTimerClicked()
|
||||||
}
|
}
|
||||||
|
@ -32,25 +32,6 @@ class GeneralSettings : BasePreferenceFragment() {
|
|||||||
RequireRestartDialog().show(childFragmentManager, RequireRestartDialog::class.java.name)
|
RequireRestartDialog().show(childFragmentManager, RequireRestartDialog::class.java.name)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
val breakReminder =
|
|
||||||
findPreference<SwitchPreferenceCompat>(PreferenceKeys.SLEEP_TIMER)
|
|
||||||
val breakReminderTime = findPreference<EditTextPreference>(PreferenceKeys.SLEEP_TIMER_DELAY)
|
|
||||||
breakReminderTime?.isEnabled = PreferenceHelper.getBoolean(
|
|
||||||
PreferenceKeys.SLEEP_TIMER,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
|
|
||||||
breakReminder?.setOnPreferenceChangeListener { _, newValue ->
|
|
||||||
breakReminderTime?.isEnabled = newValue as Boolean
|
|
||||||
RequireRestartDialog().show(childFragmentManager, RequireRestartDialog::class.java.name)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
breakReminderTime?.setOnPreferenceChangeListener { _, _ ->
|
|
||||||
RequireRestartDialog().show(childFragmentManager, RequireRestartDialog::class.java.name)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupRegionPref(preference: ListPreference) {
|
private fun setupRegionPref(preference: ListPreference) {
|
||||||
|
@ -0,0 +1,73 @@
|
|||||||
|
package com.github.libretube.ui.sheets
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.text.format.DateUtils
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.os.postDelayed
|
||||||
|
import androidx.core.view.isGone
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.github.libretube.R
|
||||||
|
import com.github.libretube.databinding.SleepTimerSheetBinding
|
||||||
|
import com.github.libretube.ui.tools.SleepTimer
|
||||||
|
|
||||||
|
class SleepTimerSheet: ExpandedBottomSheet() {
|
||||||
|
private var _binding: SleepTimerSheetBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = SleepTimerSheetBinding.inflate(layoutInflater)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
updateTimeLeftText()
|
||||||
|
|
||||||
|
binding.startSleepTimer.setOnClickListener {
|
||||||
|
val time = binding.timeInput.text.toString().toLongOrNull()
|
||||||
|
|
||||||
|
if (time == null) {
|
||||||
|
Toast.makeText(context, R.string.invalid_input, Toast.LENGTH_SHORT).show()
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
SleepTimer.setup(requireContext(), time)
|
||||||
|
|
||||||
|
updateTimeLeftText()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.stopSleepTimer.setOnClickListener {
|
||||||
|
SleepTimer.disableSleepTimer()
|
||||||
|
updateTimeLeftText()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateTimeLeftText() {
|
||||||
|
val binding = _binding ?: return
|
||||||
|
|
||||||
|
val isTimerRunning = SleepTimer.timeLeftMillis > 0
|
||||||
|
|
||||||
|
binding.timeLeft.isVisible = isTimerRunning
|
||||||
|
binding.stopSleepTimer.isVisible = isTimerRunning
|
||||||
|
binding.timeInputLayout.isGone = isTimerRunning
|
||||||
|
binding.startSleepTimer.isGone = isTimerRunning
|
||||||
|
|
||||||
|
if (!isTimerRunning) return
|
||||||
|
|
||||||
|
binding.timeLeft.text = DateUtils.formatElapsedTime(SleepTimer.timeLeftMillis / 1000)
|
||||||
|
|
||||||
|
handler.postDelayed(1000) {
|
||||||
|
updateTimeLeftText()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,52 +6,67 @@ import android.os.Looper
|
|||||||
import android.os.Process
|
import android.os.Process
|
||||||
import androidx.core.os.postDelayed
|
import androidx.core.os.postDelayed
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.constants.PreferenceKeys
|
|
||||||
import com.github.libretube.helpers.PreferenceHelper
|
|
||||||
import com.github.libretube.ui.activities.MainActivity
|
import com.github.libretube.ui.activities.MainActivity
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import java.util.Timer
|
||||||
|
import kotlin.concurrent.scheduleAtFixedRate
|
||||||
|
|
||||||
object SleepTimer {
|
object SleepTimer {
|
||||||
private val handler = Handler(Looper.getMainLooper())
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
private const val REACTION_INTERVAL = 5L
|
private const val REACTION_INTERVAL = 5L
|
||||||
|
private const val TIMER_DELAY = 1000L
|
||||||
|
private var timer: Timer? = null
|
||||||
|
var timeLeftMillis: Long = 0L
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kill the app after showing a warning after a certain amount of time
|
* Kill the app after showing a warning after a certain amount of time
|
||||||
* @param context This must not be the applicationContext!
|
*
|
||||||
|
* @param context This must not be the applicationContext, but an activity context!
|
||||||
*/
|
*/
|
||||||
fun setup(context: Context) {
|
fun setup(context: Context, delayInMinutes: Long) {
|
||||||
if (!PreferenceHelper.getBoolean(PreferenceKeys.SLEEP_TIMER, false)) return
|
if (delayInMinutes == 0L) return
|
||||||
|
|
||||||
val breakReminderPref = PreferenceHelper.getString(
|
timeLeftMillis = delayInMinutes * 60 * 1000
|
||||||
PreferenceKeys.SLEEP_TIMER_DELAY,
|
|
||||||
""
|
|
||||||
).ifEmpty { return }
|
|
||||||
|
|
||||||
handler.postDelayed(breakReminderPref.toLong() * 60 * 1000) {
|
timer = Timer()
|
||||||
var killApp = true
|
timer?.scheduleAtFixedRate(TIMER_DELAY, TIMER_DELAY) {
|
||||||
val mainActivity = context as? MainActivity ?: return@postDelayed
|
timeLeftMillis -= TIMER_DELAY
|
||||||
val snackBar = Snackbar.make(
|
if (timeLeftMillis == 0L) showTimerEndedSnackBar(context)
|
||||||
mainActivity.binding.root,
|
}
|
||||||
R.string.take_a_break,
|
}
|
||||||
Snackbar.LENGTH_INDEFINITE
|
|
||||||
)
|
/**
|
||||||
.setAction(R.string.cancel) {
|
* Disable the scheduled sleep timer
|
||||||
killApp = false
|
*/
|
||||||
}
|
fun disableSleepTimer() {
|
||||||
snackBar.show()
|
timer?.cancel()
|
||||||
for (i in 0..REACTION_INTERVAL) {
|
timeLeftMillis = 0L
|
||||||
handler.postDelayed(i * 1000) {
|
}
|
||||||
val remainingTime = " (${REACTION_INTERVAL - i})"
|
|
||||||
snackBar.setText(context.getString(R.string.take_a_break) + remainingTime)
|
private fun showTimerEndedSnackBar(context: Context) {
|
||||||
}
|
var killApp = true
|
||||||
|
val mainActivity = context as? MainActivity ?: return
|
||||||
|
val snackBar = Snackbar.make(
|
||||||
|
mainActivity.binding.root,
|
||||||
|
R.string.take_a_break,
|
||||||
|
Snackbar.LENGTH_INDEFINITE
|
||||||
|
)
|
||||||
|
.setAction(R.string.cancel) {
|
||||||
|
killApp = false
|
||||||
}
|
}
|
||||||
handler.postDelayed(REACTION_INTERVAL * 1000) {
|
snackBar.show()
|
||||||
if (killApp) {
|
for (i in 0..REACTION_INTERVAL) {
|
||||||
// kill the application
|
handler.postDelayed(i * 1000) {
|
||||||
mainActivity.finishAffinity()
|
val remainingTime = " (${REACTION_INTERVAL - i})"
|
||||||
mainActivity.finish()
|
snackBar.setText(context.getString(R.string.take_a_break) + remainingTime)
|
||||||
Process.killProcess(Process.myPid())
|
}
|
||||||
}
|
}
|
||||||
|
handler.postDelayed(REACTION_INTERVAL * 1000) {
|
||||||
|
if (killApp) {
|
||||||
|
// kill the application
|
||||||
|
mainActivity.finishAffinity()
|
||||||
|
mainActivity.finish()
|
||||||
|
Process.killProcess(Process.myPid())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,7 @@ import com.github.libretube.ui.interfaces.PlayerOptions
|
|||||||
import com.github.libretube.ui.listeners.PlayerGestureController
|
import com.github.libretube.ui.listeners.PlayerGestureController
|
||||||
import com.github.libretube.ui.sheets.BaseBottomSheet
|
import com.github.libretube.ui.sheets.BaseBottomSheet
|
||||||
import com.github.libretube.ui.sheets.PlaybackOptionsSheet
|
import com.github.libretube.ui.sheets.PlaybackOptionsSheet
|
||||||
|
import com.github.libretube.ui.sheets.SleepTimerSheet
|
||||||
import com.github.libretube.util.PlayingQueue
|
import com.github.libretube.util.PlayingQueue
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
@ -347,6 +348,12 @@ open class CustomExoPlayerView(
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
onPlaybackSpeedClicked()
|
onPlaybackSpeedClicked()
|
||||||
|
},
|
||||||
|
BottomSheetItem(
|
||||||
|
context.getString(R.string.sleep_timer),
|
||||||
|
R.drawable.ic_sleep
|
||||||
|
) {
|
||||||
|
onSleepTimerClicked()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -552,6 +559,10 @@ open class CustomExoPlayerView(
|
|||||||
.show(supportFragmentManager)
|
.show(supportFragmentManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSleepTimerClicked() {
|
||||||
|
SleepTimerSheet().show(supportFragmentManager)
|
||||||
|
}
|
||||||
|
|
||||||
open fun isFullscreen() =
|
open fun isFullscreen() =
|
||||||
resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||||
|
|
||||||
|
10
app/src/main/res/drawable/ic_sleep.xml
Normal file
10
app/src/main/res/drawable/ic_sleep.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M12.34,2.02C6.59,1.82 2,6.42 2,12c0,5.52 4.48,10 10,10c3.71,0 6.93,-2.02 8.66,-5.02C13.15,16.73 8.57,8.55 12.34,2.02z" />
|
||||||
|
</vector>
|
106
app/src/main/res/layout/sleep_timer_sheet.xml
Normal file
106
app/src/main/res/layout/sleep_timer_sheet.xml
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/standard_bottom_sheet"
|
||||||
|
style="@style/Widget.Material3.BottomSheet"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:paddingBottom="20dp"
|
||||||
|
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!-- Drag handle for accessibility -->
|
||||||
|
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
||||||
|
android:id="@+id/drag_handle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/bottom_sheet_title_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="12dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:layout_marginBottom="5dp"
|
||||||
|
android:text="@string/sleep_timer"
|
||||||
|
android:textSize="27sp" />
|
||||||
|
|
||||||
|
<com.google.android.material.divider.MaterialDivider
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/time_left"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:layout_marginVertical="10dp"
|
||||||
|
android:textColor="?colorPrimary"
|
||||||
|
android:textSize="40sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:text="20:10" />
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/time_input_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/time_in_minutes">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/time_input"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:layout_marginVertical="10dp"
|
||||||
|
android:inputType="number"
|
||||||
|
android:text="30"
|
||||||
|
tools:ignore="HardcodedText" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/start_sleep_timer"
|
||||||
|
style="@style/Widget.Material3.Button.TonalButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/start_sleep_timer" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/stop_sleep_timer"
|
||||||
|
style="@style/Widget.Material3.Button.TonalButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/disable_sleep_timer"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -496,6 +496,10 @@
|
|||||||
<string name="watched">Watched</string>
|
<string name="watched">Watched</string>
|
||||||
<string name="no_segments_found">There are no segments for this video yet.</string>
|
<string name="no_segments_found">There are no segments for this video yet.</string>
|
||||||
<string name="bottom_reached">Bottom reached!</string>
|
<string name="bottom_reached">Bottom reached!</string>
|
||||||
|
<string name="start_sleep_timer">Start sleep timer</string>
|
||||||
|
<string name="disable_sleep_timer">Disable sleep timer</string>
|
||||||
|
<string name="time_in_minutes">Time in minutes</string>
|
||||||
|
<string name="invalid_input">Invalid input</string>
|
||||||
|
|
||||||
<!-- Notification channel strings -->
|
<!-- Notification channel strings -->
|
||||||
<string name="download_channel_name">Download Service</string>
|
<string name="download_channel_name">Download Service</string>
|
||||||
|
@ -54,22 +54,4 @@
|
|||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory app:title="@string/misc">
|
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:icon="@drawable/ic_notification"
|
|
||||||
app:key="sleep_timer_toggle"
|
|
||||||
app:title="@string/sleep_timer" />
|
|
||||||
|
|
||||||
<EditTextPreference
|
|
||||||
android:enabled="false"
|
|
||||||
android:icon="@drawable/ic_time"
|
|
||||||
app:defaultValue="60"
|
|
||||||
app:key="sleep_timer_delay"
|
|
||||||
app:title="@string/break_reminder_time"
|
|
||||||
app:useSimpleSummaryProvider="true" />
|
|
||||||
|
|
||||||
</PreferenceCategory>
|
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
Loading…
x
Reference in New Issue
Block a user