This commit is contained in:
Pavol Franek 2020-03-04 08:42:19 +01:00
parent afaa3f470c
commit 046f0323d1
5 changed files with 202 additions and 148 deletions

View File

@ -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'
}

View File

@ -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)

View File

@ -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)

View File

@ -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
// }

View File

@ -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()
}
}