wip
This commit is contained in:
parent
afaa3f470c
commit
046f0323d1
|
@ -54,6 +54,16 @@ android {
|
|||
checkReleaseBuilds false
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 1.8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
freeCompilerArgs = ["-XXLanguage:+NewInference"]
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
@ -61,4 +71,8 @@ dependencies {
|
|||
implementation 'com.facebook.stetho:stetho:1.5.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2'
|
||||
implementation 'com.shawnlin:number-picker:2.4.6'
|
||||
implementation "androidx.preference:preference:1.1.0"
|
||||
implementation "androidx.work:work-runtime-ktx:2.3.2"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.appwidget.AppWidgetManager
|
|||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.media.AudioAttributes
|
||||
import android.media.AudioManager
|
||||
import android.net.Uri
|
||||
|
@ -15,6 +16,7 @@ import android.text.style.RelativeSizeSpan
|
|||
import android.widget.Toast
|
||||
import androidx.core.app.AlarmManagerCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.simplemobiletools.clock.R
|
||||
import com.simplemobiletools.clock.activities.ReminderActivity
|
||||
import com.simplemobiletools.clock.activities.SnoozeReminderActivity
|
||||
|
@ -382,3 +384,5 @@ fun Context.checkAlarmsWithDeletedSoundUri(uri: String) {
|
|||
dbHelper.updateAlarm(it)
|
||||
}
|
||||
}
|
||||
|
||||
val Context.preferences: SharedPreferences get() = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package com.simplemobiletools.clock.extensions
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.preference.PreferenceManager
|
||||
|
||||
val Fragment.requiredActivity: FragmentActivity get() = this.activity!!
|
||||
|
||||
val Fragment.preferences: SharedPreferences get() = PreferenceManager.getDefaultSharedPreferences(requiredActivity)
|
|
@ -4,6 +4,7 @@ import android.annotation.TargetApi
|
|||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.NotificationManager.IMPORTANCE_HIGH
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
|
@ -12,11 +13,16 @@ import android.os.Build
|
|||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import com.simplemobiletools.clock.R
|
||||
import com.simplemobiletools.clock.activities.ReminderActivity
|
||||
import com.simplemobiletools.clock.activities.SimpleActivity
|
||||
|
@ -24,53 +30,65 @@ import com.simplemobiletools.clock.dialogs.MyTimePickerDialogDialog
|
|||
import com.simplemobiletools.clock.extensions.*
|
||||
import com.simplemobiletools.clock.helpers.PICK_AUDIO_FILE_INTENT_ID
|
||||
import com.simplemobiletools.clock.helpers.TIMER_NOTIF_ID
|
||||
import com.simplemobiletools.clock.workers.cancelTimerWorker
|
||||
import com.simplemobiletools.clock.workers.enqueueTimerWorker
|
||||
import com.simplemobiletools.clock.workers.timerRequestId
|
||||
import com.simplemobiletools.commons.dialogs.SelectAlarmSoundDialog
|
||||
import com.simplemobiletools.commons.extensions.*
|
||||
import com.simplemobiletools.commons.helpers.ALARM_SOUND_TYPE_ALARM
|
||||
import com.simplemobiletools.commons.helpers.isOreoPlus
|
||||
import com.simplemobiletools.commons.models.AlarmSound
|
||||
import kotlinx.android.synthetic.main.fragment_timer.*
|
||||
import kotlinx.android.synthetic.main.fragment_timer.view.*
|
||||
import kotlinx.android.synthetic.main.fragment_timer.view.timer_time
|
||||
import kotlinx.coroutines.InternalCoroutinesApi
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class TimerFragment : Fragment() {
|
||||
|
||||
private val UPDATE_INTERVAL = 1000L
|
||||
private val WAS_RUNNING = "was_running"
|
||||
private val CURRENT_TICKS = "current_ticks"
|
||||
private val TOTAL_TICKS = "total_ticks"
|
||||
|
||||
private var isRunning = false
|
||||
private var uptimeAtStart = 0L
|
||||
private var initialSecs = 0
|
||||
private var totalTicks = 0
|
||||
private var currentTicks = 0
|
||||
private var updateHandler = Handler()
|
||||
private var isForegrounded = true
|
||||
|
||||
lateinit var view: ViewGroup
|
||||
|
||||
@InternalCoroutinesApi
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
val config = context!!.config
|
||||
val config = requiredActivity.config
|
||||
view = (inflater.inflate(R.layout.fragment_timer, container, false) as ViewGroup).apply {
|
||||
timer_time.setOnClickListener {
|
||||
togglePlayPause()
|
||||
val selectedDuration = config.timerSeconds
|
||||
enqueueTimerWorker(TimeUnit.SECONDS.toMillis(selectedDuration.toLong()))
|
||||
showNotification(selectedDuration.getFormattedDuration())
|
||||
}
|
||||
|
||||
timer_play_pause.setOnClickListener {
|
||||
togglePlayPause()
|
||||
val selectedDuration = config.timerSeconds
|
||||
enqueueTimerWorker(TimeUnit.SECONDS.toMillis(selectedDuration.toLong()))
|
||||
showNotification(selectedDuration.getFormattedDuration())
|
||||
}
|
||||
|
||||
timer_reset.setOnClickListener {
|
||||
context!!.hideTimerNotification()
|
||||
resetTimer()
|
||||
cancelTimerWorker()
|
||||
}
|
||||
|
||||
timer_initial_time.setOnClickListener {
|
||||
MyTimePickerDialogDialog(activity as SimpleActivity, config.timerSeconds) {
|
||||
val seconds = if (it <= 0) 10 else it
|
||||
config.timerSeconds = seconds
|
||||
timer_initial_time.text = seconds.getFormattedDuration()
|
||||
if (!isRunning) {
|
||||
resetTimer()
|
||||
}
|
||||
MyTimePickerDialogDialog(activity as SimpleActivity, config.timerSeconds) { seconds ->
|
||||
val timerSeconds = if (seconds <= 0) 10 else seconds
|
||||
config.timerSeconds = timerSeconds
|
||||
timer_initial_time.text = timerSeconds.getFormattedDuration()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,28 +99,47 @@ class TimerFragment : Fragment() {
|
|||
|
||||
timer_sound.setOnClickListener {
|
||||
SelectAlarmSoundDialog(activity as SimpleActivity, config.timerSoundUri, AudioManager.STREAM_ALARM, PICK_AUDIO_FILE_INTENT_ID,
|
||||
ALARM_SOUND_TYPE_ALARM, true, onAlarmPicked = {
|
||||
if (it != null) {
|
||||
updateAlarmSound(it)
|
||||
}
|
||||
}, onAlarmSoundDeleted = {
|
||||
if (config.timerSoundUri == it.uri) {
|
||||
val defaultAlarm = context.getDefaultAlarmSound(ALARM_SOUND_TYPE_ALARM)
|
||||
updateAlarmSound(defaultAlarm)
|
||||
}
|
||||
context.checkAlarmsWithDeletedSoundUri(it.uri)
|
||||
})
|
||||
ALARM_SOUND_TYPE_ALARM, true,
|
||||
onAlarmPicked = { sound ->
|
||||
if (sound != null) {
|
||||
updateAlarmSound(sound)
|
||||
}
|
||||
},
|
||||
onAlarmSoundDeleted = { sound ->
|
||||
if (config.timerSoundUri == sound.uri) {
|
||||
val defaultAlarm = context.getDefaultAlarmSound(ALARM_SOUND_TYPE_ALARM)
|
||||
updateAlarmSound(defaultAlarm)
|
||||
}
|
||||
|
||||
context.checkAlarmsWithDeletedSoundUri(sound.uri)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
initialSecs = config.timerSeconds
|
||||
updateDisplayedText()
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
isForegrounded = true
|
||||
WorkManager.getInstance(activity!!).getWorkInfoByIdLiveData(timerRequestId)
|
||||
.observe(this, Observer { workInfo ->
|
||||
Log.e("test", workInfo.toString())
|
||||
|
||||
when (workInfo.state) {
|
||||
WorkInfo.State.SUCCEEDED -> {
|
||||
showNotification("-")
|
||||
}
|
||||
WorkInfo.State.FAILED -> {}
|
||||
WorkInfo.State.CANCELLED -> {}
|
||||
else -> {}
|
||||
}
|
||||
})
|
||||
|
||||
val a = lifecycleScope.launch {
|
||||
(config.timerSeconds downTo 0).asFlow().onEach { delay(1000) }.collect {
|
||||
Log.e("test", it.toString())
|
||||
timer_time.text = it.getFormattedDuration()
|
||||
}
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
@ -110,28 +147,12 @@ class TimerFragment : Fragment() {
|
|||
setupViews()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
isForegrounded = false
|
||||
context!!.hideNotification(TIMER_NOTIF_ID)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
if (isRunning && activity?.isChangingConfigurations == false) {
|
||||
context?.toast(R.string.timer_stopped)
|
||||
}
|
||||
isRunning = false
|
||||
updateHandler.removeCallbacks(updateRunnable)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
outState.apply {
|
||||
super.onSaveInstanceState(outState.apply {
|
||||
putBoolean(WAS_RUNNING, isRunning)
|
||||
putInt(TOTAL_TICKS, totalTicks)
|
||||
putInt(CURRENT_TICKS, currentTicks)
|
||||
}
|
||||
super.onSaveInstanceState(outState)
|
||||
})
|
||||
}
|
||||
|
||||
override fun onViewStateRestored(savedInstanceState: Bundle?) {
|
||||
|
@ -140,25 +161,21 @@ class TimerFragment : Fragment() {
|
|||
isRunning = getBoolean(WAS_RUNNING, false)
|
||||
totalTicks = getInt(TOTAL_TICKS, 0)
|
||||
currentTicks = getInt(CURRENT_TICKS, 0)
|
||||
|
||||
if (isRunning) {
|
||||
uptimeAtStart = SystemClock.uptimeMillis() - currentTicks * UPDATE_INTERVAL
|
||||
updateTimerState(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateAlarmSound(alarmSound: AlarmSound) {
|
||||
context!!.config.timerSoundTitle = alarmSound.title
|
||||
context!!.config.timerSoundUri = alarmSound.uri
|
||||
requiredActivity.config.timerSoundTitle = alarmSound.title
|
||||
requiredActivity.config.timerSoundUri = alarmSound.uri
|
||||
view.timer_sound.text = alarmSound.title
|
||||
}
|
||||
|
||||
private fun setupViews() {
|
||||
val config = context!!.config
|
||||
val config = requiredActivity.config
|
||||
val textColor = config.textColor
|
||||
|
||||
view.apply {
|
||||
context!!.updateTextColors(timer_fragment)
|
||||
requiredActivity.updateTextColors(timer_fragment)
|
||||
timer_play_pause.background = resources.getColoredDrawableWithColor(R.drawable.circle_background_filled, context!!.getAdjustedPrimaryColor())
|
||||
timer_reset.applyColorFilter(textColor)
|
||||
|
||||
|
@ -173,84 +190,19 @@ class TimerFragment : Fragment() {
|
|||
}
|
||||
|
||||
updateIcons()
|
||||
updateDisplayedText()
|
||||
}
|
||||
|
||||
private fun togglePlayPause() {
|
||||
isRunning = !isRunning
|
||||
updateTimerState(true)
|
||||
}
|
||||
|
||||
private fun updateTimerState(setUptimeAtStart: Boolean) {
|
||||
updateIcons()
|
||||
context!!.hideTimerNotification()
|
||||
|
||||
if (isRunning) {
|
||||
updateHandler.post(updateRunnable)
|
||||
view.timer_reset.beVisible()
|
||||
if (setUptimeAtStart) {
|
||||
uptimeAtStart = SystemClock.uptimeMillis()
|
||||
}
|
||||
} else {
|
||||
updateHandler.removeCallbacksAndMessages(null)
|
||||
currentTicks = 0
|
||||
totalTicks--
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateIcons() {
|
||||
val drawableId = if (isRunning) R.drawable.ic_pause_vector else R.drawable.ic_play_vector
|
||||
val iconColor = if (context!!.getAdjustedPrimaryColor() == Color.WHITE) Color.BLACK else context!!.config.textColor
|
||||
val iconColor = if (requiredActivity.getAdjustedPrimaryColor() == Color.WHITE) Color.BLACK else requiredActivity.config.textColor
|
||||
view.timer_play_pause.setImageDrawable(resources.getColoredDrawableWithColor(drawableId, iconColor))
|
||||
}
|
||||
|
||||
private fun resetTimer() {
|
||||
updateHandler.removeCallbacks(updateRunnable)
|
||||
isRunning = false
|
||||
currentTicks = 0
|
||||
totalTicks = 0
|
||||
initialSecs = context!!.config.timerSeconds
|
||||
updateDisplayedText()
|
||||
updateIcons()
|
||||
view.timer_reset.beGone()
|
||||
}
|
||||
|
||||
private fun updateDisplayedText(): Boolean {
|
||||
val diff = initialSecs - totalTicks
|
||||
var formattedDuration = Math.abs(diff).getFormattedDuration()
|
||||
|
||||
if (diff < 0) {
|
||||
formattedDuration = "-$formattedDuration"
|
||||
if (!isForegrounded) {
|
||||
resetTimer()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
view.timer_time.text = formattedDuration
|
||||
if (diff == 0) {
|
||||
if (context?.isScreenOn() == true) {
|
||||
context!!.showTimerNotification(false)
|
||||
Handler().postDelayed({
|
||||
context?.hideTimerNotification()
|
||||
}, context?.config!!.timerMaxReminderSecs * 1000L)
|
||||
} else {
|
||||
Intent(context, ReminderActivity::class.java).apply {
|
||||
activity?.startActivity(this)
|
||||
}
|
||||
}
|
||||
} else if (diff > 0 && !isForegrounded && isRunning) {
|
||||
showNotification(formattedDuration)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
private fun showNotification(formattedDuration: String) {
|
||||
val channelId = "simple_alarm_timer"
|
||||
val label = getString(R.string.timer)
|
||||
val notificationManager = context!!.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
val notificationManager = requiredActivity.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
if (isOreoPlus()) {
|
||||
val importance = NotificationManager.IMPORTANCE_HIGH
|
||||
NotificationChannel(channelId, label, importance).apply {
|
||||
|
@ -260,29 +212,62 @@ class TimerFragment : Fragment() {
|
|||
}
|
||||
|
||||
val builder = NotificationCompat.Builder(context)
|
||||
.setContentTitle(label)
|
||||
.setContentText(formattedDuration)
|
||||
.setSmallIcon(R.drawable.ic_timer)
|
||||
.setContentIntent(context!!.getOpenTimerTabIntent())
|
||||
.setPriority(Notification.PRIORITY_HIGH)
|
||||
.setSound(null)
|
||||
.setOngoing(true)
|
||||
.setAutoCancel(true)
|
||||
.setChannelId(channelId)
|
||||
.setContentTitle(label)
|
||||
.setContentText(formattedDuration)
|
||||
.setSmallIcon(R.drawable.ic_timer)
|
||||
.setContentIntent(context!!.getOpenTimerTabIntent())
|
||||
.setPriority(Notification.PRIORITY_HIGH)
|
||||
.setSound(null)
|
||||
.setOngoing(true)
|
||||
.setAutoCancel(true)
|
||||
.setChannelId(channelId)
|
||||
|
||||
builder.setVisibility(Notification.VISIBILITY_PUBLIC)
|
||||
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
notificationManager.notify(TIMER_NOTIF_ID, builder.build())
|
||||
}
|
||||
|
||||
private val updateRunnable = object : Runnable {
|
||||
override fun run() {
|
||||
if (isRunning) {
|
||||
if (updateDisplayedText()) {
|
||||
currentTicks++
|
||||
totalTicks++
|
||||
updateHandler.postAtTime(this, uptimeAtStart + currentTicks * UPDATE_INTERVAL)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// private fun resetTimer() {
|
||||
// updateHandler.removeCallbacks(updateRunnable)
|
||||
// isRunning = false
|
||||
// currentTicks = 0
|
||||
// totalTicks = 0
|
||||
// initialSecs = context!!.config.timerSeconds
|
||||
// updateDisplayedText()
|
||||
// updateIcons()
|
||||
// view.timer_reset.beGone()
|
||||
// requiredActivity.hideTimerNotification()
|
||||
// context!!.hideNotification(TIMER_NOTIF_ID)
|
||||
// context?.toast(R.string.timer_stopped)
|
||||
// }
|
||||
|
||||
// private fun updateDisplayedText(): Boolean {
|
||||
// val diff = initialSecs - totalTicks
|
||||
// var formattedDuration = Math.abs(diff).getFormattedDuration()
|
||||
//
|
||||
// if (diff < 0) {
|
||||
// formattedDuration = "-$formattedDuration"
|
||||
// if (!isForegrounded) {
|
||||
// resetTimer()
|
||||
// return false
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// view.timer_time.text = formattedDuration
|
||||
// if (diff == 0) {
|
||||
// if (context?.isScreenOn() == true) {
|
||||
// context!!.showTimerNotification(false)
|
||||
// Handler().postDelayed({
|
||||
// context?.hideTimerNotification()
|
||||
// }, context?.config!!.timerMaxReminderSecs * 1000L)
|
||||
// } else {
|
||||
// Intent(context, ReminderActivity::class.java).apply {
|
||||
// activity?.startActivity(this)
|
||||
// }
|
||||
// }
|
||||
// } else if (diff > 0 && !isForegrounded && isRunning) {
|
||||
// showNotification(formattedDuration)
|
||||
// }
|
||||
//
|
||||
// return true
|
||||
// }
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
package com.simplemobiletools.clock.workers
|
||||
|
||||
import android.content.Context
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.work.*
|
||||
import com.simplemobiletools.clock.extensions.preferences
|
||||
import com.simplemobiletools.clock.extensions.requiredActivity
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
private const val TIMER_REQUEST_ID = "TIMER_REQUEST_ID"
|
||||
private const val TIMER_WORKER_KEY = "TIMER_WORKER_KEY"
|
||||
|
||||
private fun Fragment.saveTimerRequestId(uuid: UUID) =
|
||||
preferences.edit().putString(TIMER_REQUEST_ID, uuid.toString()).apply()
|
||||
|
||||
val Fragment.timerRequestId: UUID get() =
|
||||
UUID.fromString(preferences.getString(TIMER_REQUEST_ID, UUID.randomUUID().toString()))
|
||||
|
||||
fun Fragment.cancelTimerWorker() =
|
||||
WorkManager.getInstance(requiredActivity).apply {
|
||||
timerRequestId.let(::cancelWorkById)
|
||||
}
|
||||
|
||||
fun Fragment.enqueueTimerWorker(delay: Long) =
|
||||
WorkManager.getInstance(requiredActivity).enqueueUniqueWork(TIMER_WORKER_KEY, ExistingWorkPolicy.REPLACE, timerRequest(delay))
|
||||
|
||||
private fun Fragment.timerRequest(delay: Long) =
|
||||
OneTimeWorkRequestBuilder<TimerWorker>().setInitialDelay(delay, TimeUnit.MILLISECONDS).build().also {
|
||||
saveTimerRequestId(it.id)
|
||||
}
|
||||
|
||||
class TimerWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
|
||||
override fun doWork(): Result =
|
||||
try {
|
||||
Result.success()
|
||||
} catch (exception: Exception) {
|
||||
Result.failure()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue