Skip to content

Commit db5b553

Browse files
committed
Add sleep timer
This closes #105
1 parent 10c3913 commit db5b553

File tree

60 files changed

+507
-50
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+507
-50
lines changed

app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ android {
7676
}
7777

7878
dependencies {
79-
implementation 'com.github.SimpleMobileTools:Simple-Commons:7c1e5b5777'
79+
implementation 'com.github.SimpleMobileTools:Simple-Commons:f64d11af6f'
8080
implementation 'org.greenrobot:eventbus:3.3.1'
8181
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
8282
}

app/src/main/AndroidManifest.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
<uses-permission android:name="android.permission.FLASHLIGHT" />
1212

13+
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
1314
<uses-permission
1415
android:name="android.permission.USE_FINGERPRINT"
1516
tools:node="remove" />
@@ -132,6 +133,9 @@
132133
android:resource="@xml/widget_bright_display" />
133134
</receiver>
134135

136+
<receiver android:name=".helpers.ShutDownReceiver"
137+
android:exported="false" />
138+
135139
<service
136140
android:name=".helpers.FlashlightTileService"
137141
android:exported="true"

app/src/main/kotlin/com/simplemobiletools/flashlight/activities/BrightDisplayActivity.kt

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ import android.graphics.drawable.ColorDrawable
55
import android.os.Bundle
66
import android.view.WindowManager
77
import com.simplemobiletools.commons.dialogs.ColorPickerDialog
8-
import com.simplemobiletools.commons.extensions.applyColorFilter
9-
import com.simplemobiletools.commons.extensions.getContrastColor
10-
import com.simplemobiletools.commons.extensions.viewBinding
8+
import com.simplemobiletools.commons.extensions.*
119
import com.simplemobiletools.flashlight.databinding.ActivityBrightDisplayBinding
1210
import com.simplemobiletools.flashlight.extensions.config
11+
import com.simplemobiletools.flashlight.helpers.stopSleepTimerCountDown
12+
import com.simplemobiletools.flashlight.models.Events
13+
import org.greenrobot.eventbus.EventBus
14+
import org.greenrobot.eventbus.Subscribe
15+
import org.greenrobot.eventbus.ThreadMode
1316

1417
class BrightDisplayActivity : SimpleActivity() {
1518
private val binding by viewBinding(ActivityBrightDisplayBinding::inflate)
@@ -45,6 +48,8 @@ class BrightDisplayActivity : SimpleActivity() {
4548
}
4649
}
4750
}
51+
52+
binding.sleepTimerStop.setOnClickListener { stopSleepTimer() }
4853
}
4954

5055
override fun onResume() {
@@ -60,6 +65,16 @@ class BrightDisplayActivity : SimpleActivity() {
6065
toggleBrightness(false)
6166
}
6267

68+
override fun onStart() {
69+
super.onStart()
70+
EventBus.getDefault().register(this)
71+
}
72+
73+
override fun onStop() {
74+
super.onStop()
75+
EventBus.getDefault().unregister(this)
76+
}
77+
6378
private fun setBackgroundColor(color: Int) {
6479
binding.apply {
6580
brightDisplay.background = ColorDrawable(color)
@@ -77,4 +92,19 @@ class BrightDisplayActivity : SimpleActivity() {
7792
layout.screenBrightness = (if (increase) 1 else 0).toFloat()
7893
window.attributes = layout
7994
}
95+
96+
private fun stopSleepTimer() {
97+
binding.sleepTimerHolder.fadeOut()
98+
stopSleepTimerCountDown()
99+
}
100+
101+
@Subscribe(threadMode = ThreadMode.MAIN)
102+
fun sleepTimerChanged(event: Events.SleepTimerChanged) {
103+
binding.sleepTimerValue.text = event.seconds.getFormattedDuration()
104+
binding.sleepTimerHolder.beVisible()
105+
106+
if (event.seconds == 0) {
107+
finish()
108+
}
109+
}
80110
}

app/src/main/kotlin/com/simplemobiletools/flashlight/activities/MainActivity.kt

Lines changed: 87 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.simplemobiletools.flashlight.activities
22

33
import android.annotation.SuppressLint
4+
import android.app.AlarmManager
5+
import android.content.Context
46
import android.content.Intent
57
import android.content.pm.ActivityInfo
68
import android.content.pm.ShortcutInfo
@@ -9,22 +11,22 @@ import android.graphics.drawable.LayerDrawable
911
import android.os.Bundle
1012
import android.view.WindowManager
1113
import android.widget.ImageView
14+
import com.simplemobiletools.commons.dialogs.PermissionRequiredDialog
15+
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
1216
import com.simplemobiletools.commons.extensions.*
13-
import com.simplemobiletools.commons.helpers.LICENSE_EVENT_BUS
14-
import com.simplemobiletools.commons.helpers.PERMISSION_CAMERA
15-
import com.simplemobiletools.commons.helpers.isNougatMR1Plus
16-
import com.simplemobiletools.commons.helpers.isNougatPlus
17+
import com.simplemobiletools.commons.helpers.*
1718
import com.simplemobiletools.commons.models.FAQItem
19+
import com.simplemobiletools.commons.models.RadioItem
1820
import com.simplemobiletools.flashlight.BuildConfig
1921
import com.simplemobiletools.flashlight.R
2022
import com.simplemobiletools.flashlight.databinding.ActivityMainBinding
23+
import com.simplemobiletools.flashlight.dialogs.SleepTimerCustomDialog
2124
import com.simplemobiletools.flashlight.extensions.config
22-
import com.simplemobiletools.flashlight.helpers.CameraTorchListener
23-
import com.simplemobiletools.flashlight.helpers.MIN_BRIGHTNESS_LEVEL
24-
import com.simplemobiletools.flashlight.helpers.MyCameraImpl
25+
import com.simplemobiletools.flashlight.helpers.*
2526
import com.simplemobiletools.flashlight.models.Events
2627
import org.greenrobot.eventbus.EventBus
2728
import org.greenrobot.eventbus.Subscribe
29+
import org.greenrobot.eventbus.ThreadMode
2830
import java.util.*
2931

3032
class MainActivity : SimpleActivity() {
@@ -74,6 +76,8 @@ class MainActivity : SimpleActivity() {
7476
stroboscopeBtn.setOnClickListener {
7577
toggleStroboscope(false)
7678
}
79+
80+
sleepTimerStop.setOnClickListener { stopSleepTimer() }
7781
}
7882

7983
setupStroboscope()
@@ -126,6 +130,11 @@ class MainActivity : SimpleActivity() {
126130
super.onStart()
127131
mBus!!.register(this)
128132

133+
if (config.sleepInTS == 0L) {
134+
binding.sleepTimerHolder.beGone()
135+
(getSystemService(Context.ALARM_SERVICE) as AlarmManager).cancel(getShutDownPendingIntent())
136+
}
137+
129138
if (mCameraImpl == null) {
130139
setupCameraImpl()
131140
}
@@ -146,6 +155,7 @@ class MainActivity : SimpleActivity() {
146155
when (menuItem.itemId) {
147156
R.id.more_apps_from_us -> launchMoreAppsFromUsIntent()
148157
R.id.settings -> launchSettings()
158+
R.id.sleep_timer -> showSleepTimer()
149159
R.id.about -> launchAbout()
150160
else -> return@setOnMenuItemClickListener false
151161
}
@@ -279,6 +289,76 @@ class MainActivity : SimpleActivity() {
279289
mCameraImpl = null
280290
}
281291

292+
private fun showSleepTimer(force: Boolean = false) {
293+
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
294+
if (isSPlus() && !alarmManager.canScheduleExactAlarms() && !force) {
295+
PermissionRequiredDialog(
296+
this,
297+
com.simplemobiletools.commons.R.string.allow_alarm_sleep_timer,
298+
positiveActionCallback = { openRequestExactAlarmSettings(baseConfig.appId) },
299+
negativeActionCallback = { showSleepTimer(true) }
300+
)
301+
return
302+
}
303+
val minutes = getString(R.string.minutes_raw)
304+
val hour = resources.getQuantityString(R.plurals.hours, 1, 1)
305+
306+
val items = arrayListOf(
307+
RadioItem(5 * 60, "5 $minutes"),
308+
RadioItem(10 * 60, "10 $minutes"),
309+
RadioItem(20 * 60, "20 $minutes"),
310+
RadioItem(30 * 60, "30 $minutes"),
311+
RadioItem(60 * 60, hour)
312+
)
313+
314+
if (items.none { it.id == config.lastSleepTimerSeconds }) {
315+
val lastSleepTimerMinutes = config.lastSleepTimerSeconds / 60
316+
val text = resources.getQuantityString(R.plurals.minutes, lastSleepTimerMinutes, lastSleepTimerMinutes)
317+
items.add(RadioItem(config.lastSleepTimerSeconds, text))
318+
}
319+
320+
items.sortBy { it.id }
321+
items.add(RadioItem(-1, getString(R.string.custom)))
322+
323+
RadioGroupDialog(this, items, config.lastSleepTimerSeconds) {
324+
if (it as Int == -1) {
325+
SleepTimerCustomDialog(this) {
326+
if (it > 0) {
327+
pickedSleepTimer(it)
328+
}
329+
}
330+
} else if (it > 0) {
331+
pickedSleepTimer(it)
332+
}
333+
}
334+
}
335+
336+
private fun pickedSleepTimer(seconds: Int) {
337+
config.lastSleepTimerSeconds = seconds
338+
config.sleepInTS = System.currentTimeMillis() + seconds * 1000
339+
startSleepTimer()
340+
}
341+
342+
private fun startSleepTimer() {
343+
binding.sleepTimerHolder.fadeIn()
344+
startSleepTimerCountDown()
345+
}
346+
347+
private fun stopSleepTimer() {
348+
binding.sleepTimerHolder.fadeOut()
349+
stopSleepTimerCountDown()
350+
}
351+
352+
@Subscribe(threadMode = ThreadMode.MAIN)
353+
fun sleepTimerChanged(event: Events.SleepTimerChanged) {
354+
binding.sleepTimerValue.text = event.seconds.getFormattedDuration()
355+
binding.sleepTimerHolder.beVisible()
356+
357+
if (event.seconds == 0) {
358+
finish()
359+
}
360+
}
361+
282362
@Subscribe
283363
fun stateChangedEvent(event: Events.StateChanged) {
284364
checkState(event.isEnabled)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.simplemobiletools.flashlight.dialogs
2+
3+
import android.app.Activity
4+
import androidx.appcompat.app.AlertDialog
5+
import com.simplemobiletools.commons.extensions.*
6+
import com.simplemobiletools.flashlight.R
7+
import com.simplemobiletools.flashlight.databinding.DialogCustomSleepTimerPickerBinding
8+
9+
class SleepTimerCustomDialog(val activity: Activity, val callback: (seconds: Int) -> Unit) {
10+
private var dialog: AlertDialog? = null
11+
private val binding = DialogCustomSleepTimerPickerBinding.inflate(activity.layoutInflater)
12+
13+
init {
14+
binding.minutesHint.hint = activity.getString(R.string.minutes_raw).replaceFirstChar { it.uppercaseChar() }
15+
activity.getAlertDialogBuilder()
16+
.setPositiveButton(R.string.ok) { dialog, which -> dialogConfirmed() }
17+
.setNegativeButton(R.string.cancel, null)
18+
.apply {
19+
activity.setupDialogStuff(binding.root, this, R.string.sleep_timer) { alertDialog ->
20+
dialog = alertDialog
21+
alertDialog.showKeyboard(binding.minutes)
22+
}
23+
}
24+
}
25+
26+
private fun dialogConfirmed() {
27+
val value = binding.minutes.value
28+
val minutes = Integer.valueOf(if (value.isEmpty()) "0" else value)
29+
callback(minutes * 60)
30+
activity.hideKeyboard()
31+
dialog?.dismiss()
32+
}
33+
}

app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/Config.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,12 @@ class Config(context: Context) : BaseConfig(context) {
4444
var brightnessLevel: Int
4545
get() = prefs.getInt(BRIGHTNESS_LEVEL, DEFAULT_BRIGHTNESS_LEVEL)
4646
set(brightnessLevel) = prefs.edit().putInt(BRIGHTNESS_LEVEL, brightnessLevel).apply()
47+
48+
var lastSleepTimerSeconds: Int
49+
get() = prefs.getInt(LAST_SLEEP_TIMER_SECONDS, 30 * 60)
50+
set(lastSleepTimerSeconds) = prefs.edit().putInt(LAST_SLEEP_TIMER_SECONDS, lastSleepTimerSeconds).apply()
51+
52+
var sleepInTS: Long
53+
get() = prefs.getLong(SLEEP_IN_TS, 0)
54+
set(sleepInTS) = prefs.edit().putLong(SLEEP_IN_TS, sleepInTS).apply()
4755
}

app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/Constants.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,7 @@ const val STROBOSCOPE_PROGRESS = "stroboscope_progress"
1212
const val FORCE_PORTRAIT_MODE = "force_portrait_mode"
1313
const val SOS = "sos"
1414
const val BRIGHTNESS_LEVEL = "brightness_level"
15+
const val LAST_SLEEP_TIMER_SECONDS = "last_sleep_timer_seconds"
16+
const val SLEEP_IN_TS = "sleep_in_ts"
1517
const val MIN_BRIGHTNESS_LEVEL = 1
1618
const val DEFAULT_BRIGHTNESS_LEVEL = -1
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.simplemobiletools.flashlight.helpers
2+
3+
import android.content.BroadcastReceiver
4+
import android.content.Context
5+
import android.content.Intent
6+
import kotlin.system.exitProcess
7+
8+
class ShutDownReceiver : BroadcastReceiver() {
9+
override fun onReceive(p0: Context?, p1: Intent?) {
10+
exitProcess(0)
11+
}
12+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.simplemobiletools.flashlight.helpers
2+
3+
import android.app.AlarmManager
4+
import android.app.PendingIntent
5+
import android.content.Context
6+
import android.content.Intent
7+
import android.os.CountDownTimer
8+
import com.simplemobiletools.commons.helpers.isSPlus
9+
import com.simplemobiletools.flashlight.extensions.config
10+
import com.simplemobiletools.flashlight.models.Events
11+
import org.greenrobot.eventbus.EventBus
12+
import kotlin.system.exitProcess
13+
14+
private var isActive = false
15+
private var sleepTimer: CountDownTimer? = null
16+
17+
internal fun Context.toggleSleepTimer() {
18+
if (isActive) {
19+
stopSleepTimerCountDown()
20+
} else {
21+
startSleepTimerCountDown()
22+
}
23+
}
24+
25+
internal fun Context.startSleepTimerCountDown() {
26+
val millisInFuture = config.sleepInTS - System.currentTimeMillis() + 1000L
27+
(getSystemService(Context.ALARM_SERVICE) as AlarmManager).apply {
28+
if (!isSPlus() || canScheduleExactAlarms()) {
29+
setExactAndAllowWhileIdle(
30+
AlarmManager.RTC_WAKEUP,
31+
config.sleepInTS,
32+
getShutDownPendingIntent()
33+
)
34+
} else {
35+
setAndAllowWhileIdle(
36+
AlarmManager.RTC_WAKEUP,
37+
config.sleepInTS,
38+
getShutDownPendingIntent()
39+
)
40+
}
41+
}
42+
sleepTimer?.cancel()
43+
sleepTimer = object : CountDownTimer(millisInFuture, 1000) {
44+
override fun onTick(millisUntilFinished: Long) {
45+
val seconds = (millisUntilFinished / 1000).toInt()
46+
EventBus.getDefault().post(Events.SleepTimerChanged(seconds))
47+
}
48+
49+
override fun onFinish() {
50+
config.sleepInTS = 0
51+
EventBus.getDefault().post(Events.SleepTimerChanged(0))
52+
stopSleepTimerCountDown()
53+
exitProcess(0)
54+
}
55+
}
56+
57+
sleepTimer?.start()
58+
isActive = true
59+
}
60+
61+
internal fun Context.stopSleepTimerCountDown() {
62+
(getSystemService(Context.ALARM_SERVICE) as AlarmManager).cancel(getShutDownPendingIntent())
63+
sleepTimer?.cancel()
64+
sleepTimer = null
65+
isActive = false
66+
config.sleepInTS = 0
67+
}
68+
69+
internal fun Context.getShutDownPendingIntent() =
70+
PendingIntent.getBroadcast(this, 0, Intent(this, ShutDownReceiver::class.java), PendingIntent.FLAG_IMMUTABLE)

app/src/main/kotlin/com/simplemobiletools/flashlight/models/Events.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@ class Events {
88
class StopStroboscope
99

1010
class StopSOS
11+
12+
class SleepTimerChanged(val seconds: Int)
1113
}

0 commit comments

Comments
 (0)