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 REGION = "region"
|
||||
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 NAVBAR_ITEMS = "navbar_items"
|
||||
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.SearchViewModel
|
||||
import com.github.libretube.ui.models.SubscriptionsViewModel
|
||||
import com.github.libretube.ui.tools.SleepTimer
|
||||
import com.google.android.material.elevation.SurfaceColors
|
||||
|
||||
class MainActivity : BaseActivity() {
|
||||
@ -138,8 +137,6 @@ class MainActivity : BaseActivity() {
|
||||
ErrorDialog().show(supportFragmentManager, null)
|
||||
}
|
||||
|
||||
SleepTimer.setup(this)
|
||||
|
||||
setupSubscriptionsBadge()
|
||||
|
||||
// new way of handling back presses
|
||||
|
@ -7,4 +7,5 @@ interface PlayerOptions {
|
||||
fun onResizeModeClicked()
|
||||
|
||||
fun onRepeatModeClicked()
|
||||
fun onSleepTimerClicked()
|
||||
}
|
||||
|
@ -32,25 +32,6 @@ class GeneralSettings : BasePreferenceFragment() {
|
||||
RequireRestartDialog().show(childFragmentManager, RequireRestartDialog::class.java.name)
|
||||
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) {
|
||||
|
@ -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 androidx.core.os.postDelayed
|
||||
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.google.android.material.snackbar.Snackbar
|
||||
import java.util.Timer
|
||||
import kotlin.concurrent.scheduleAtFixedRate
|
||||
|
||||
object SleepTimer {
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
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
|
||||
* @param context This must not be the applicationContext!
|
||||
*
|
||||
* @param context This must not be the applicationContext, but an activity context!
|
||||
*/
|
||||
fun setup(context: Context) {
|
||||
if (!PreferenceHelper.getBoolean(PreferenceKeys.SLEEP_TIMER, false)) return
|
||||
fun setup(context: Context, delayInMinutes: Long) {
|
||||
if (delayInMinutes == 0L) return
|
||||
|
||||
val breakReminderPref = PreferenceHelper.getString(
|
||||
PreferenceKeys.SLEEP_TIMER_DELAY,
|
||||
""
|
||||
).ifEmpty { return }
|
||||
timeLeftMillis = delayInMinutes * 60 * 1000
|
||||
|
||||
handler.postDelayed(breakReminderPref.toLong() * 60 * 1000) {
|
||||
var killApp = true
|
||||
val mainActivity = context as? MainActivity ?: return@postDelayed
|
||||
val snackBar = Snackbar.make(
|
||||
mainActivity.binding.root,
|
||||
R.string.take_a_break,
|
||||
Snackbar.LENGTH_INDEFINITE
|
||||
)
|
||||
.setAction(R.string.cancel) {
|
||||
killApp = false
|
||||
}
|
||||
snackBar.show()
|
||||
for (i in 0..REACTION_INTERVAL) {
|
||||
handler.postDelayed(i * 1000) {
|
||||
val remainingTime = " (${REACTION_INTERVAL - i})"
|
||||
snackBar.setText(context.getString(R.string.take_a_break) + remainingTime)
|
||||
}
|
||||
timer = Timer()
|
||||
timer?.scheduleAtFixedRate(TIMER_DELAY, TIMER_DELAY) {
|
||||
timeLeftMillis -= TIMER_DELAY
|
||||
if (timeLeftMillis == 0L) showTimerEndedSnackBar(context)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the scheduled sleep timer
|
||||
*/
|
||||
fun disableSleepTimer() {
|
||||
timer?.cancel()
|
||||
timeLeftMillis = 0L
|
||||
}
|
||||
|
||||
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) {
|
||||
if (killApp) {
|
||||
// kill the application
|
||||
mainActivity.finishAffinity()
|
||||
mainActivity.finish()
|
||||
Process.killProcess(Process.myPid())
|
||||
}
|
||||
snackBar.show()
|
||||
for (i in 0..REACTION_INTERVAL) {
|
||||
handler.postDelayed(i * 1000) {
|
||||
val remainingTime = " (${REACTION_INTERVAL - i})"
|
||||
snackBar.setText(context.getString(R.string.take_a_break) + remainingTime)
|
||||
}
|
||||
}
|
||||
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.sheets.BaseBottomSheet
|
||||
import com.github.libretube.ui.sheets.PlaybackOptionsSheet
|
||||
import com.github.libretube.ui.sheets.SleepTimerSheet
|
||||
import com.github.libretube.util.PlayingQueue
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@ -347,6 +348,12 @@ open class CustomExoPlayerView(
|
||||
}
|
||||
) {
|
||||
onPlaybackSpeedClicked()
|
||||
},
|
||||
BottomSheetItem(
|
||||
context.getString(R.string.sleep_timer),
|
||||
R.drawable.ic_sleep
|
||||
) {
|
||||
onSleepTimerClicked()
|
||||
}
|
||||
)
|
||||
|
||||
@ -552,6 +559,10 @@ open class CustomExoPlayerView(
|
||||
.show(supportFragmentManager)
|
||||
}
|
||||
|
||||
override fun onSleepTimerClicked() {
|
||||
SleepTimerSheet().show(supportFragmentManager)
|
||||
}
|
||||
|
||||
open fun isFullscreen() =
|
||||
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="no_segments_found">There are no segments for this video yet.</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 -->
|
||||
<string name="download_channel_name">Download Service</string>
|
||||
|
@ -54,22 +54,4 @@
|
||||
|
||||
</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>
|
Loading…
x
Reference in New Issue
Block a user