Setup adding/removing multiple timers

This commit is contained in:
Paul Akhamiogu 2021-08-31 21:18:45 +01:00
parent 49f0da8123
commit 921ca92885
14 changed files with 554 additions and 206 deletions

View File

@ -1,6 +1,7 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
def keystorePropertiesFile = rootProject.file("keystore.properties") def keystorePropertiesFile = rootProject.file("keystore.properties")
def keystoreProperties = new Properties() def keystoreProperties = new Properties()
@ -76,4 +77,6 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
implementation 'org.greenrobot:eventbus:3.2.0' implementation 'org.greenrobot:eventbus:3.2.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.room:room-runtime:2.3.0'
kapt 'androidx.room:room-compiler:2.3.0'
} }

View File

@ -0,0 +1,144 @@
package com.simplemobiletools.clock.adapters
import android.media.AudioManager
import android.media.RingtoneManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.simplemobiletools.clock.R
import com.simplemobiletools.clock.activities.SimpleActivity
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.models.Timer
import com.simplemobiletools.clock.models.TimerState
import com.simplemobiletools.commons.dialogs.SelectAlarmSoundDialog
import com.simplemobiletools.commons.extensions.getDefaultAlarmSound
import com.simplemobiletools.commons.extensions.getFormattedDuration
import com.simplemobiletools.commons.extensions.onTextChangeListener
import com.simplemobiletools.commons.models.AlarmSound
import kotlinx.android.synthetic.main.item_timer.view.*
import org.greenrobot.eventbus.EventBus
class TimerAdapter(
private val activity: SimpleActivity,
private val onRefresh: () -> Unit,
) : ListAdapter<Timer, TimerAdapter.TimerViewHolder>(diffUtil) {
private val config = activity.config
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TimerViewHolder {
return TimerViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_timer, parent, false))
}
override fun onBindViewHolder(holder: TimerViewHolder, position: Int) {
holder.bind(getItem(position))
}
fun getItemAt(position: Int): Timer {
return getItem(position)
}
inner class TimerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(timer: Timer) {
itemView.apply {
timer_time.text = timer.seconds.getFormattedDuration()
timer_label.setText(timer.label)
timer_time.text = timer.seconds.getFormattedDuration()
timer_label.setText(timer.label)
val textColor = activity.config.textColor
timer_initial_time.text = timer.seconds.getFormattedDuration()
timer_initial_time.colorLeftDrawable(textColor)
timer_vibrate.isChecked = timer.vibrate
timer_vibrate.colorLeftDrawable(textColor)
timer_sound.text = timer.soundTitle
timer_sound.colorLeftDrawable(textColor)
timer_time.setOnClickListener {
stopTimer(timer)
}
timer_time.setOnClickListener {
changeDuration(timer)
}
timer_initial_time.setOnClickListener {
changeDuration(timer)
}
timer_vibrate_holder.setOnClickListener {
timer_vibrate.toggle()
updateTimer(timer.copy(vibrate = timer_vibrate.isChecked), false)
}
timer_sound.setOnClickListener {
SelectAlarmSoundDialog(activity, config.timerSoundUri, AudioManager.STREAM_ALARM, PICK_AUDIO_FILE_INTENT_ID,
RingtoneManager.TYPE_ALARM, true,
onAlarmPicked = { sound ->
if (sound != null) {
updateAlarmSound(timer, sound)
}
},
onAlarmSoundDeleted = { sound ->
if (timer.soundUri == sound.uri) {
val defaultAlarm = context.getDefaultAlarmSound(RingtoneManager.TYPE_ALARM)
updateAlarmSound(timer, defaultAlarm)
}
context.checkAlarmsWithDeletedSoundUri(sound.uri)
})
}
timer_label.onTextChangeListener { text ->
updateTimer(timer.copy(label = text), false)
}
}
}
}
private fun stopTimer(timer: Timer) {
EventBus.getDefault().post(TimerState.Idle)
activity.hideTimerNotification()
}
private fun changeDuration(timer: Timer) {
MyTimePickerDialogDialog(activity, timer.seconds) { seconds ->
val timerSeconds = if (seconds <= 0) 10 else seconds
updateTimer(timer.copy(seconds = timerSeconds))
}
}
fun updateAlarmSound(timer: Timer, alarmSound: AlarmSound) {
updateTimer(timer.copy(soundTitle = alarmSound.title, soundUri = alarmSound.uri))
}
private fun updateTimer(timer: Timer, refresh: Boolean = true) {
activity.timerHelper.insertOrUpdateTimer(timer)
if (refresh) {
onRefresh.invoke()
}
}
companion object {
val diffUtil = object : DiffUtil.ItemCallback<Timer>() {
override fun areItemsTheSame(oldItem: Timer, newItem: Timer): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Timer, newItem: Timer): Boolean {
return oldItem == newItem
}
}
}
}

View File

@ -11,7 +11,6 @@ import com.simplemobiletools.clock.fragments.TimerFragment
import com.simplemobiletools.clock.helpers.TABS_COUNT import com.simplemobiletools.clock.helpers.TABS_COUNT
import com.simplemobiletools.clock.helpers.TAB_ALARM import com.simplemobiletools.clock.helpers.TAB_ALARM
import com.simplemobiletools.clock.helpers.TAB_CLOCK import com.simplemobiletools.clock.helpers.TAB_CLOCK
import com.simplemobiletools.clock.helpers.TAB_TIMER
import com.simplemobiletools.commons.models.AlarmSound import com.simplemobiletools.commons.models.AlarmSound
class ViewPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) { class ViewPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
@ -51,6 +50,6 @@ class ViewPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
} }
fun updateTimerTabAlarmSound(alarmSound: AlarmSound) { fun updateTimerTabAlarmSound(alarmSound: AlarmSound) {
(fragments[TAB_TIMER] as? TimerFragment)?.updateAlarmSound(alarmSound) // (fragments[TAB_TIMER] as? TimerFragment)?.updateAlarmSound(alarmSound)
} }
} }

View File

@ -0,0 +1,38 @@
package com.simplemobiletools.clock.databases
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import com.simplemobiletools.clock.helpers.Converters
import com.simplemobiletools.clock.interfaces.TimerDao
import com.simplemobiletools.clock.models.Timer
@Database(entities = [Timer::class], version = 1)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun TimerDao(): TimerDao
companion object {
private var db: AppDatabase? = null
fun getInstance(context: Context): AppDatabase {
if (db == null) {
synchronized(AppDatabase::class) {
if (db == null) {
db = Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app.db")
.build()
db!!.openHelper.setWriteAheadLoggingEnabled(true)
}
}
}
return db!!
}
fun destroyInstance() {
db = null
}
}
}

View File

@ -20,7 +20,9 @@ import com.simplemobiletools.clock.R
import com.simplemobiletools.clock.activities.ReminderActivity import com.simplemobiletools.clock.activities.ReminderActivity
import com.simplemobiletools.clock.activities.SnoozeReminderActivity import com.simplemobiletools.clock.activities.SnoozeReminderActivity
import com.simplemobiletools.clock.activities.SplashActivity import com.simplemobiletools.clock.activities.SplashActivity
import com.simplemobiletools.clock.databases.AppDatabase
import com.simplemobiletools.clock.helpers.* import com.simplemobiletools.clock.helpers.*
import com.simplemobiletools.clock.interfaces.TimerDao
import com.simplemobiletools.clock.models.Alarm import com.simplemobiletools.clock.models.Alarm
import com.simplemobiletools.clock.models.MyTimeZone import com.simplemobiletools.clock.models.MyTimeZone
import com.simplemobiletools.clock.receivers.AlarmReceiver import com.simplemobiletools.clock.receivers.AlarmReceiver
@ -39,6 +41,8 @@ import kotlin.math.pow
val Context.config: Config get() = Config.newInstance(applicationContext) val Context.config: Config get() = Config.newInstance(applicationContext)
val Context.dbHelper: DBHelper get() = DBHelper.newInstance(applicationContext) val Context.dbHelper: DBHelper get() = DBHelper.newInstance(applicationContext)
val Context.timerDb: TimerDao get() = AppDatabase.getInstance(applicationContext).TimerDao()
val Context.timerHelper: TimerHelper get() = TimerHelper(this)
fun Context.getFormattedDate(calendar: Calendar): String { fun Context.getFormattedDate(calendar: Calendar): String {
val dayOfWeek = (calendar.get(Calendar.DAY_OF_WEEK) + 5) % 7 // make sure index 0 means monday val dayOfWeek = (calendar.get(Calendar.DAY_OF_WEEK) + 5) % 7 // make sure index 0 means monday

View File

@ -1,31 +1,31 @@
package com.simplemobiletools.clock.fragments package com.simplemobiletools.clock.fragments
import android.graphics.Color import android.graphics.Color
import android.media.AudioManager
import android.media.RingtoneManager
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.viewpager2.widget.ViewPager2
import com.simplemobiletools.clock.R import com.simplemobiletools.clock.R
import com.simplemobiletools.clock.activities.SimpleActivity import com.simplemobiletools.clock.activities.SimpleActivity
import com.simplemobiletools.clock.adapters.TimerAdapter
import com.simplemobiletools.clock.dialogs.MyTimePickerDialogDialog import com.simplemobiletools.clock.dialogs.MyTimePickerDialogDialog
import com.simplemobiletools.clock.extensions.* import com.simplemobiletools.clock.extensions.config
import com.simplemobiletools.clock.helpers.PICK_AUDIO_FILE_INTENT_ID import com.simplemobiletools.clock.extensions.hideTimerNotification
import com.simplemobiletools.clock.extensions.timerHelper
import com.simplemobiletools.clock.models.Timer
import com.simplemobiletools.clock.models.TimerState import com.simplemobiletools.clock.models.TimerState
import com.simplemobiletools.commons.dialogs.SelectAlarmSoundDialog
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.models.AlarmSound
import kotlinx.android.synthetic.main.fragment_timer.view.* import kotlinx.android.synthetic.main.fragment_timer.view.*
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode import org.greenrobot.eventbus.ThreadMode
import kotlin.math.roundToInt
class TimerFragment : Fragment() { class TimerFragment : Fragment() {
lateinit var view: ViewGroup lateinit var view: ViewGroup
private lateinit var timerAdapter: TimerAdapter
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -39,91 +39,82 @@ class TimerFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
view = (inflater.inflate(R.layout.fragment_timer, container, false) as ViewGroup).apply { view = (inflater.inflate(R.layout.fragment_timer, container, false) as ViewGroup).apply {
val config = requiredActivity.config timerAdapter = TimerAdapter(requireActivity() as SimpleActivity) {
val textColor = config.textColor refreshTimers()
}
timer_view_pager.adapter = timerAdapter
timer_view_pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
updateViews(position)
}
})
timer_time.text = config.timerSeconds.getFormattedDuration() timer_add.setOnClickListener {
timer_label.setText(config.timerLabel) activity?.hideKeyboard(it)
activity?.timerHelper?.insertNewTimer {
activity?.updateTextColors(timer_fragment) refreshTimers(true)
timer_play_pause.background = resources.getColoredDrawableWithColor(R.drawable.circle_background_filled, requireContext().getAdjustedPrimaryColor()) }
timer_play_pause.applyColorFilter(if (requireContext().getAdjustedPrimaryColor() == Color.WHITE) Color.BLACK else Color.WHITE)
timer_reset.applyColorFilter(textColor)
timer_initial_time.text = config.timerSeconds.getFormattedDuration()
timer_initial_time.colorLeftDrawable(textColor)
timer_vibrate.isChecked = config.timerVibrate
timer_vibrate.colorLeftDrawable(textColor)
timer_sound.text = config.timerSoundTitle
timer_sound.colorLeftDrawable(textColor)
timer_time.setOnClickListener {
stopTimer()
} }
timer_play_pause.setOnClickListener { activity?.updateTextColors(timer_fragment)
val state = config.timerState
when (state) { val textColor = requireContext().config.textColor
is TimerState.Idle -> EventBus.getDefault().post(TimerState.Start(config.timerSeconds.secondsToMillis)) timer_play_pause.background =
is TimerState.Paused -> EventBus.getDefault().post(TimerState.Start(state.tick)) resources.getColoredDrawableWithColor(R.drawable.circle_background_filled, requireActivity().getAdjustedPrimaryColor())
is TimerState.Running -> EventBus.getDefault().post(TimerState.Pause(state.tick)) timer_play_pause.applyColorFilter(if (activity?.getAdjustedPrimaryColor() == Color.WHITE) Color.BLACK else Color.WHITE)
is TimerState.Finished -> EventBus.getDefault().post(TimerState.Start(config.timerSeconds.secondsToMillis)) timer_reset.applyColorFilter(textColor)
timer_play_pause.setOnClickListener {
val timer = timerAdapter.getItemAt(timer_view_pager.currentItem)
when (val state = timer.state) {
// is TimerState.Idle -> EventBus.getDefault().post(TimerState.Start(timer.seconds.secondsToMillis))
// is TimerState.Paused -> EventBus.getDefault().post(TimerState.Start(state.tick))
// is TimerState.Running -> EventBus.getDefault().post(TimerState.Pause(state.tick))
// is TimerState.Finished -> EventBus.getDefault().post(TimerState.Start(timer.seconds.secondsToMillis))
else -> { else -> {
} }
} }
} }
timer_reset.setOnClickListener { timer_reset.setOnClickListener {
stopTimer() val timer = timerAdapter.getItemAt(timer_view_pager.currentItem)
stopTimer(timer)
} }
timer_time.setOnClickListener { timer_delete.setOnClickListener {
changeDuration() val timer = timerAdapter.getItemAt(timer_view_pager.currentItem)
activity?.timerHelper?.deleteTimer(timer.id!!) {
refreshTimers()
}
} }
timer_initial_time.setOnClickListener { refreshTimers()
changeDuration()
}
timer_vibrate_holder.setOnClickListener {
timer_vibrate.toggle()
config.timerVibrate = timer_vibrate.isChecked
config.timerChannelId = null
}
timer_sound.setOnClickListener {
SelectAlarmSoundDialog(activity as SimpleActivity, config.timerSoundUri, AudioManager.STREAM_ALARM, PICK_AUDIO_FILE_INTENT_ID,
RingtoneManager.TYPE_ALARM, true,
onAlarmPicked = { sound ->
if (sound != null) {
updateAlarmSound(sound)
}
},
onAlarmSoundDeleted = { sound ->
if (config.timerSoundUri == sound.uri) {
val defaultAlarm = context.getDefaultAlarmSound(RingtoneManager.TYPE_ALARM)
updateAlarmSound(defaultAlarm)
}
context.checkAlarmsWithDeletedSoundUri(sound.uri)
})
}
timer_label.onTextChangeListener { text ->
config.timerLabel = text
}
} }
return view return view
} }
private fun stopTimer() { private fun updateViews(position: Int) {
val timer = timerAdapter.getItemAt(position)
//check if timer is running to update view
}
private fun refreshTimers(scrollToLast: Boolean = false) {
activity?.timerHelper?.getTimers { timers ->
timerAdapter.submitList(timers)
activity?.runOnUiThread {
view.timer_delete.beVisibleIf(timers.size > 1)
if (scrollToLast) {
view.timer_view_pager.currentItem = timers.lastIndex
}
}
}
}
private fun stopTimer(timer: Timer) {
EventBus.getDefault().post(TimerState.Idle) EventBus.getDefault().post(TimerState.Idle)
activity?.hideTimerNotification() activity?.hideTimerNotification()
view.timer_time.text = activity?.config?.timerSeconds?.getFormattedDuration() // view.timer_time.text = activity?.config?.timerSeconds?.getFormattedDuration()
} }
private fun changeDuration() { private fun changeDuration() {
@ -131,23 +122,23 @@ class TimerFragment : Fragment() {
val timerSeconds = if (seconds <= 0) 10 else seconds val timerSeconds = if (seconds <= 0) 10 else seconds
activity?.config?.timerSeconds = timerSeconds activity?.config?.timerSeconds = timerSeconds
val duration = timerSeconds.getFormattedDuration() val duration = timerSeconds.getFormattedDuration()
view.timer_initial_time.text = duration // view.timer_initial_time.text = duration
if (view.timer_reset.isGone()) { // if (view.timer_reset.isGone()) {
stopTimer() // stopTimer()
} // }
} }
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(state: TimerState.Idle) { fun onMessageEvent(state: TimerState.Idle) {
view.timer_time.text = requiredActivity.config.timerSeconds.getFormattedDuration() // view.timer_time.text = requiredActivity.config.timerSeconds.getFormattedDuration()
updateViewStates(state) updateViewStates(state)
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(state: TimerState.Running) { fun onMessageEvent(state: TimerState.Running) {
view.timer_time.text = state.tick.div(1000F).roundToInt().getFormattedDuration() // view.timer_time.text = state.tick.div(1000F).roundToInt().getFormattedDuration()
updateViewStates(state) updateViewStates(state)
} }
@ -158,7 +149,7 @@ class TimerFragment : Fragment() {
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(state: TimerState.Finished) { fun onMessageEvent(state: TimerState.Finished) {
view.timer_time.text = 0.getFormattedDuration() // view.timer_time.text = 0.getFormattedDuration()
updateViewStates(state) updateViewStates(state)
} }
@ -180,10 +171,10 @@ class TimerFragment : Fragment() {
view.timer_play_pause.setImageDrawable(resources.getColoredDrawableWithColor(drawableId, iconColor)) view.timer_play_pause.setImageDrawable(resources.getColoredDrawableWithColor(drawableId, iconColor))
} }
//
fun updateAlarmSound(alarmSound: AlarmSound) { // fun updateAlarmSound(alarmSound: AlarmSound) {
activity?.config?.timerSoundTitle = alarmSound.title // activity?.config?.timerSoundTitle = alarmSound.title
activity?.config?.timerSoundUri = alarmSound.uri // activity?.config?.timerSoundUri = alarmSound.uri
view.timer_sound.text = alarmSound.title // view.timer_sound.text = alarmSound.title
} // }
} }

View File

@ -1,7 +1,9 @@
package com.simplemobiletools.clock.helpers package com.simplemobiletools.clock.helpers
import com.simplemobiletools.clock.models.MyTimeZone import com.simplemobiletools.clock.models.MyTimeZone
import java.util.* import java.util.Calendar
import java.util.Date
import java.util.TimeZone
import kotlin.math.pow import kotlin.math.pow
// shared preferences // shared preferences
@ -56,6 +58,8 @@ const val SORT_BY_ALARM_TIME = 1
const val TODAY_BIT = -1 const val TODAY_BIT = -1
const val TOMORROW_BIT = -2 const val TOMORROW_BIT = -2
const val DEFAULT_TIME = 300
fun getDefaultTimeZoneTitle(id: Int) = getAllTimeZones().firstOrNull { it.id == id }?.title ?: "" fun getDefaultTimeZoneTitle(id: Int) = getAllTimeZones().firstOrNull { it.id == id }?.title ?: ""
fun getMSTillNextMinute(): Long { fun getMSTillNextMinute(): Long {

View File

@ -0,0 +1,22 @@
package com.simplemobiletools.clock.helpers
import androidx.room.TypeConverter
import com.google.gson.Gson
import com.simplemobiletools.clock.models.StateWrapper
import com.simplemobiletools.clock.models.TimerState
class Converters {
private val gson = Gson()
@TypeConverter
fun jsonToTimerState(value: String): TimerState {
return try {
gson.fromJson(value, StateWrapper::class.java).state
} catch (e: Exception) {
TimerState.Idle
}
}
@TypeConverter
fun timerStateToJson(state: TimerState) = gson.toJson(StateWrapper(state))
}

View File

@ -0,0 +1,51 @@
package com.simplemobiletools.clock.helpers
import android.content.Context
import android.media.RingtoneManager
import com.simplemobiletools.clock.extensions.timerDb
import com.simplemobiletools.clock.models.Timer
import com.simplemobiletools.clock.models.TimerState
import com.simplemobiletools.commons.extensions.getDefaultAlarmSound
import com.simplemobiletools.commons.extensions.getDefaultAlarmTitle
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
class TimerHelper(val context: Context) {
private val timerDao = context.timerDb
fun getTimers(callback: (notes: List<Timer>) -> Unit) {
ensureBackgroundThread {
callback.invoke(timerDao.getTimers())
}
}
fun insertOrUpdateTimer(timer: Timer, callback: () -> Unit = {}) {
ensureBackgroundThread {
timerDao.insertOrUpdateTimer(timer)
callback.invoke()
}
}
fun deleteTimer(id: Long, callback: () -> Unit = {}) {
ensureBackgroundThread {
timerDao.deleteTimer(id)
callback.invoke()
}
}
fun insertNewTimer(callback: () -> Unit = {}) {
ensureBackgroundThread {
timerDao.insertOrUpdateTimer(
Timer(id = null,
seconds = DEFAULT_TIME,
TimerState.Idle,
false,
context.getDefaultAlarmSound(RingtoneManager.TYPE_ALARM).uri,
context.getDefaultAlarmTitle(RingtoneManager.TYPE_ALARM),
"",
DEFAULT_MAX_TIMER_REMINDER_SECS.toString())
)
callback.invoke()
}
}
}

View File

@ -0,0 +1,20 @@
package com.simplemobiletools.clock.interfaces
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.simplemobiletools.clock.models.Timer
@Dao
interface TimerDao {
@Query("SELECT * FROM timers")
fun getTimers(): List<Timer>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertOrUpdateTimer(timer: Timer): Long
@Query("DELETE FROM timers WHERE id=:id")
fun deleteTimer(id: Long)
}

View File

@ -0,0 +1,16 @@
package com.simplemobiletools.clock.models
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "timers")
data class Timer(
@PrimaryKey(autoGenerate = true) val id: Long?,
val seconds: Int,
val state: TimerState,
val vibrate: Boolean,
val soundUri: String,
val soundTitle: String,
val label: String,
val maxReminderSecs: String,
)

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"
android:strokeWidth="1.6"
android:strokeColor="#FFFFFFFF" />
</vector>

View File

@ -6,127 +6,16 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<ScrollView <androidx.viewpager2.widget.ViewPager2
android:id="@+id/timer_scroll" android:id="@+id/timer_view_pager"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/timer_play_pause" android:layout_marginBottom="@dimen/medium_margin"
android:orientation="vertical"
app:layout_constraintBottom_toTopOf="@id/timer_play_pause"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/timer_constraint"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/timer_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/normal_margin"
android:background="?attr/selectableItemBackground"
android:gravity="center_horizontal"
android:padding="@dimen/small_margin"
android:textSize="@dimen/stopwatch_text_size"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="00:00" />
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/timer_initial_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_margin"
android:background="?attr/selectableItemBackground"
android:drawableLeft="@drawable/ic_timer"
android:drawablePadding="@dimen/normal_margin"
android:padding="@dimen/activity_margin"
android:textSize="@dimen/bigger_text_size"
app:layout_constraintTop_toBottomOf="@+id/timer_time"
tools:text="05:00" />
<RelativeLayout
android:id="@+id/timer_vibrate_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:padding="@dimen/activity_margin"
app:layout_constraintTop_toBottomOf="@+id/timer_initial_time">
<com.simplemobiletools.commons.views.MySwitchCompat
android:id="@+id/timer_vibrate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:clickable="false"
android:drawableLeft="@drawable/ic_vibrate_vector"
android:drawablePadding="@dimen/normal_margin"
android:text="@string/vibrate"
android:textSize="@dimen/bigger_text_size" />
</RelativeLayout>
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/timer_sound"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:drawableLeft="@drawable/ic_bell_vector"
android:drawablePadding="@dimen/normal_margin"
android:padding="@dimen/activity_margin"
android:textSize="@dimen/bigger_text_size"
app:layout_constraintTop_toBottomOf="@+id/timer_vibrate_holder"
tools:text="Default alarm" />
<LinearLayout
android:id="@+id/timer_label_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="@dimen/activity_margin"
android:paddingTop="@dimen/medium_margin"
android:paddingEnd="@dimen/activity_margin"
android:paddingBottom="@dimen/medium_margin"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/timer_sound">
<ImageView
android:id="@+id/timer_label_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_label_vector" />
<com.simplemobiletools.commons.views.MyEditText
android:id="@+id/timer_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:hint="@string/label"
android:maxLines="1"
android:singleLine="true"
android:textCursorDrawable="@null"
android:textSize="@dimen/normal_text_size" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
<ImageView
android:id="@+id/timer_play_pause"
android:layout_width="@dimen/stopwatch_button_size"
android:layout_height="@dimen/stopwatch_button_size"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="@dimen/big_margin"
android:padding="@dimen/activity_margin"
android:src="@drawable/ic_play_vector"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageView <ImageView
android:id="@+id/timer_reset" android:id="@+id/timer_reset"
@ -140,8 +29,53 @@
app:layout_constraintEnd_toStartOf="@+id/timer_play_pause" app:layout_constraintEnd_toStartOf="@+id/timer_play_pause"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/timer_play_pause" app:layout_constraintTop_toTopOf="@+id/timer_play_pause"
tools:visibility="gone" />
<ImageView
android:id="@+id/timer_delete"
android:layout_width="@dimen/stopwatch_button_small_size"
android:layout_height="@dimen/stopwatch_button_small_size"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="@dimen/normal_margin"
android:src="@drawable/ic_delete_vector"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="@+id/timer_play_pause"
app:layout_constraintEnd_toStartOf="@+id/timer_play_pause"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/timer_play_pause"
tools:visibility="visible" /> tools:visibility="visible" />
<ImageView
android:id="@+id/timer_play_pause"
android:layout_width="@dimen/stopwatch_button_size"
android:layout_height="@dimen/stopwatch_button_size"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="@dimen/big_margin"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="@dimen/activity_margin"
android:src="@drawable/ic_play_vector"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="@+id/timer_add"
android:layout_width="@dimen/stopwatch_button_size"
android:layout_height="@dimen/stopwatch_button_size"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="@dimen/big_margin"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="@dimen/activity_margin"
android:src="@drawable/ic_add_vector"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/timer_play_pause" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView 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"
android:fillViewport="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/timer_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/normal_margin"
android:background="?attr/selectableItemBackground"
android:gravity="center_horizontal"
android:padding="@dimen/small_margin"
android:textSize="@dimen/stopwatch_text_size"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="00:00" />
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/timer_initial_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_margin"
android:background="?attr/selectableItemBackground"
android:drawableStart="@drawable/ic_timer"
android:drawablePadding="@dimen/normal_margin"
android:padding="@dimen/activity_margin"
android:textSize="@dimen/bigger_text_size"
app:layout_constraintTop_toBottomOf="@+id/timer_time"
tools:text="05:00" />
<RelativeLayout
android:id="@+id/timer_vibrate_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:padding="@dimen/activity_margin"
app:layout_constraintTop_toBottomOf="@+id/timer_initial_time">
<com.simplemobiletools.commons.views.MySwitchCompat
android:id="@+id/timer_vibrate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:clickable="false"
android:drawableStart="@drawable/ic_vibrate_vector"
android:drawablePadding="@dimen/normal_margin"
android:text="@string/vibrate"
android:textSize="@dimen/bigger_text_size" />
</RelativeLayout>
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/timer_sound"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:drawableStart="@drawable/ic_bell_vector"
android:drawablePadding="@dimen/normal_margin"
android:padding="@dimen/activity_margin"
android:textSize="@dimen/bigger_text_size"
app:layout_constraintTop_toBottomOf="@+id/timer_vibrate_holder"
tools:text="Default alarm" />
<LinearLayout
android:id="@+id/timer_label_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="@dimen/activity_margin"
android:paddingTop="@dimen/medium_margin"
android:paddingEnd="@dimen/activity_margin"
android:paddingBottom="@dimen/medium_margin"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/timer_sound"
app:layout_constraintVertical_bias="0"
app:layout_constraintVertical_chainStyle="spread_inside">
<ImageView
android:id="@+id/timer_label_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_label_vector" />
<com.simplemobiletools.commons.views.MyEditText
android:id="@+id/timer_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:hint="@string/label"
android:maxLines="1"
android:singleLine="true"
android:textCursorDrawable="@null"
android:textSize="@dimen/normal_text_size" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>