Implement action mode, select to delete

This commit is contained in:
Paul Akhamiogu 2021-09-07 23:15:33 +01:00
parent e1357ecd8f
commit db3e0c9d07
15 changed files with 154 additions and 112 deletions

View file

@ -67,7 +67,8 @@ android {
}
dependencies {
implementation 'com.github.SimpleMobileTools:Simple-Commons:554dda71a4'
// implementation 'com.github.SimpleMobileTools:Simple-Commons:554dda71a4'
implementation project(":commons")
implementation 'com.facebook.stetho:stetho:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.shawnlin:number-picker:2.4.6'

View file

@ -26,7 +26,7 @@ import org.greenrobot.eventbus.ThreadMode
class App : Application(), LifecycleObserver {
private var countDownTimers = mutableMapOf<Long, CountDownTimer>()
private var countDownTimers = mutableMapOf<Int, CountDownTimer>()
override fun onCreate() {
super.onCreate()
@ -108,7 +108,7 @@ class App : Application(), LifecycleObserver {
}
}
private fun updateTimerState(timerId: Long, state: TimerState) {
private fun updateTimerState(timerId: Int, state: TimerState) {
timerHelper.getTimer(timerId) { timer ->
val newTimer = timer.copy(state = state)
timerHelper.insertOrUpdateTimer(newTimer) {

View file

@ -104,7 +104,7 @@ class MainActivity : SimpleActivity() {
val tabToOpen = intent.getIntExtra(OPEN_TAB, TAB_CLOCK)
view_pager.setCurrentItem(tabToOpen, false)
if (tabToOpen == TAB_TIMER) {
val timerId = intent.getLongExtra(TIMER_ID, INVALID_TIMER_ID)
val timerId = intent.getIntExtra(TIMER_ID, INVALID_TIMER_ID)
(view_pager.adapter as ViewPagerAdapter).updateTimerPosition(timerId)
}
}
@ -152,7 +152,7 @@ class MainActivity : SimpleActivity() {
val tabToOpen = intent.getIntExtra(OPEN_TAB, config.lastUsedViewPagerPage)
intent.removeExtra(OPEN_TAB)
if (tabToOpen == TAB_TIMER) {
val timerId = intent.getLongExtra(TIMER_ID, INVALID_TIMER_ID)
val timerId = intent.getIntExtra(TIMER_ID, INVALID_TIMER_ID)
viewPagerAdapter.updateTimerPosition(timerId)
}
view_pager.currentItem = tabToOpen

View file

@ -16,7 +16,7 @@ class SplashActivity : BaseSplashActivity() {
intent.extras?.containsKey(OPEN_TAB) == true -> {
Intent(this, MainActivity::class.java).apply {
putExtra(OPEN_TAB, intent.getIntExtra(OPEN_TAB, TAB_CLOCK))
putExtra(TIMER_ID, intent.getLongExtra(TIMER_ID, INVALID_TIMER_ID))
putExtra(TIMER_ID, intent.getIntExtra(TIMER_ID, INVALID_TIMER_ID))
startActivity(this)
}
}

View file

@ -1,28 +1,32 @@
package com.simplemobiletools.clock.adapters
import android.graphics.Color
import android.view.LayoutInflater
import android.view.Menu
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.extensions.getFormattedDuration
import com.simplemobiletools.clock.extensions.hideTimerNotification
import com.simplemobiletools.clock.extensions.secondsToMillis
import com.simplemobiletools.clock.extensions.timerHelper
import com.simplemobiletools.clock.models.Timer
import com.simplemobiletools.clock.models.TimerEvent
import com.simplemobiletools.clock.models.TimerState
import com.simplemobiletools.commons.adapters.MyRecyclerViewListAdapter
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.views.MyRecyclerView
import kotlinx.android.synthetic.main.item_timer.view.*
import org.greenrobot.eventbus.EventBus
class TimerAdapter(
private val activity: SimpleActivity,
private val onRefresh: () -> Unit,
private val onClick: (Timer) -> Unit,
) : ListAdapter<Timer, TimerAdapter.TimerViewHolder>(diffUtil) {
private val simpleActivity: SimpleActivity,
recyclerView: MyRecyclerView,
onRefresh: () -> Unit,
private val onItemClick: (Timer) -> Unit,
) : MyRecyclerViewListAdapter<Timer>(simpleActivity, recyclerView, diffUtil, null, onItemClick, onRefresh) {
companion object {
private val diffUtil = object : DiffUtil.ItemCallback<Timer>() {
@ -36,101 +40,130 @@ class TimerAdapter(
}
}
private val config = activity.config
private var textColor = config.textColor
private var adjustedPrimaryColor = activity.getAdjustedPrimaryColor()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TimerViewHolder {
return TimerViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_timer, parent, false))
init {
setupDragListener(true)
}
override fun onBindViewHolder(holder: TimerViewHolder, position: Int) {
holder.bind(getItem(position))
}
override fun getActionMenuId() = R.menu.cab_alarms
fun updateTextColor(textColor: Int) {
this.textColor = textColor
onRefresh.invoke()
}
override fun prepareActionMode(menu: Menu) {}
inner class TimerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(timer: Timer) {
itemView.apply {
post {
timer_play_pause.background = activity.resources.getColoredDrawableWithColor(R.drawable.circle_background_filled, adjustedPrimaryColor)
timer_play_pause.applyColorFilter(if (adjustedPrimaryColor == Color.WHITE) Color.BLACK else Color.WHITE)
timer_reset.applyColorFilter(textColor)
timer_delete.applyColorFilter(textColor)
}
timer_label.setTextColor(textColor)
timer_label.setHintTextColor(textColor.adjustAlpha(0.7f))
//only update when different to prevent flickering and unnecessary updates
if (timer_label.text.toString() != timer.label) {
timer_label.setText(timer.label)
}
timer_time.setTextColor(textColor)
timer_time.text = when (timer.state) {
is TimerState.Finished -> 0.getFormattedDuration()
is TimerState.Idle -> timer.seconds.getFormattedDuration()
is TimerState.Paused -> timer.state.tick.getFormattedDuration()
is TimerState.Running -> timer.state.tick.getFormattedDuration()
}
timer_time.setOnClickListener {
changeDuration(timer)
}
timer_delete.applyColorFilter(textColor)
timer_delete.setOnClickListener {
activity.timerHelper.deleteTimer(timer.id!!) {
onRefresh.invoke()
}
}
timer_reset.applyColorFilter(textColor)
timer_reset.setOnClickListener {
stopTimer(timer)
}
timer_play_pause.setOnClickListener {
when (val state = timer.state) {
is TimerState.Idle -> EventBus.getDefault().post(TimerEvent.Start(timer.id!!, timer.seconds.secondsToMillis))
is TimerState.Paused -> EventBus.getDefault().post(TimerEvent.Start(timer.id!!, state.tick))
is TimerState.Running -> EventBus.getDefault().post(TimerEvent.Pause(timer.id!!, state.tick))
is TimerState.Finished -> EventBus.getDefault().post(TimerEvent.Start(timer.id!!, timer.seconds.secondsToMillis))
}
}
updateViewStates(timer.state)
setOnClickListener {
onClick.invoke(timer)
}
}
override fun actionItemPressed(id: Int) {
if (selectedKeys.isEmpty()) {
return
}
private fun updateViewStates(state: TimerState) {
when (id) {
R.id.cab_delete -> deleteItems()
}
}
override fun getSelectableItemCount() = itemCount
override fun getIsItemSelectable(position: Int) = true
override fun getItemSelectionKey(position: Int) = getItem(position).id
override fun getItemKeyPosition(key: Int): Int {
var position = -1
for (i in 0 until itemCount) {
if (key == getItem(i).id) {
position = i
break
}
}
return position
}
override fun onActionModeCreated() {}
override fun onActionModeDestroyed() {}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = createViewHolder(R.layout.item_timer, parent)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bindView(getItem(position), true, true) { itemView, _ ->
setupView(itemView, getItem(position))
}
bindViewHolder(holder)
}
private fun deleteItems() {
val positions = getSelectedItemPositions()
val timersToRemove = positions.map { position ->
getItem(position)
}
removeSelectedItems(positions)
activity.timerHelper.deleteTimers(timersToRemove) {
onRefresh.invoke()
}
}
private fun setupView(view: View, timer: Timer) {
view.apply {
val isSelected = selectedKeys.contains(timer.id)
timer_frame.isSelected = isSelected
timer_label.setTextColor(textColor)
timer_label.setHintTextColor(textColor.adjustAlpha(0.7f))
timer_label.text = timer.label
timer_time.setTextColor(textColor)
timer_time.text = when (timer.state) {
is TimerState.Finished -> 0.getFormattedDuration()
is TimerState.Idle -> timer.seconds.getFormattedDuration()
is TimerState.Paused -> timer.state.tick.getFormattedDuration()
is TimerState.Running -> timer.state.tick.getFormattedDuration()
}
timer_time.setOnClickListener {
changeDuration(timer)
}
timer_delete.applyColorFilter(textColor)
timer_delete.setOnClickListener {
simpleActivity.timerHelper.deleteTimer(timer.id!!) {
onRefresh.invoke()
}
}
timer_reset.applyColorFilter(textColor)
timer_reset.setOnClickListener {
stopTimer(timer)
}
timer_play_pause.background = simpleActivity.resources.getColoredDrawableWithColor(R.drawable.circle_background_filled, adjustedPrimaryColor)
timer_play_pause.applyColorFilter(if (adjustedPrimaryColor == Color.WHITE) Color.BLACK else Color.WHITE)
timer_play_pause.setOnClickListener {
when (val state = timer.state) {
is TimerState.Idle -> EventBus.getDefault().post(TimerEvent.Start(timer.id!!, timer.seconds.secondsToMillis))
is TimerState.Paused -> EventBus.getDefault().post(TimerEvent.Start(timer.id!!, state.tick))
is TimerState.Running -> EventBus.getDefault().post(TimerEvent.Pause(timer.id!!, state.tick))
is TimerState.Finished -> EventBus.getDefault().post(TimerEvent.Start(timer.id!!, timer.seconds.secondsToMillis))
}
}
val state = timer.state
val resetPossible = state is TimerState.Running || state is TimerState.Paused || state is TimerState.Finished
itemView.timer_reset.beInvisibleIf(!resetPossible)
itemView.timer_delete.beInvisibleIf(!(!resetPossible && itemCount > 1))
timer_reset.beInvisibleIf(!resetPossible)
timer_delete.beInvisibleIf(!(!resetPossible && itemCount > 1))
val drawableId = if (state is TimerState.Running) R.drawable.ic_pause_vector else R.drawable.ic_play_vector
val iconColor = if (adjustedPrimaryColor == Color.WHITE) Color.BLACK else Color.WHITE
itemView.timer_play_pause.setImageDrawable(activity.resources.getColoredDrawableWithColor(drawableId, iconColor))
timer_play_pause.setImageDrawable(simpleActivity.resources.getColoredDrawableWithColor(drawableId, iconColor))
}
}
private fun changeDuration(timer: Timer) {
MyTimePickerDialogDialog(activity, timer.seconds) { seconds ->
MyTimePickerDialogDialog(simpleActivity, timer.seconds) { seconds ->
val timerSeconds = if (seconds <= 0) 10 else seconds
updateTimer(timer.copy(seconds = timerSeconds))
}
}
private fun updateTimer(timer: Timer, refresh: Boolean = true) {
activity.timerHelper.insertOrUpdateTimer(timer) {
simpleActivity.timerHelper.insertOrUpdateTimer(timer) {
if (refresh) {
onRefresh.invoke()
}
@ -139,7 +172,7 @@ class TimerAdapter(
private fun stopTimer(timer: Timer) {
EventBus.getDefault().post(TimerEvent.Reset(timer.id!!, timer.seconds.secondsToMillis))
activity.hideTimerNotification()
simpleActivity.hideTimerNotification()
}
}

View file

@ -54,7 +54,7 @@ class ViewPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
(fragments[TAB_TIMER] as? TimerFragment)?.updateAlarmSound(alarmSound)
}
fun updateTimerPosition(timerId: Long) {
fun updateTimerPosition(timerId: Int) {
(fragments[TAB_TIMER] as? TimerFragment)?.updatePosition(timerId)
}
}

View file

@ -144,7 +144,7 @@ fun Context.getOpenAlarmTabIntent(): PendingIntent {
return PendingIntent.getActivity(this, OPEN_ALARMS_TAB_INTENT_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
fun Context.getOpenTimerTabIntent(timerId: Long): PendingIntent {
fun Context.getOpenTimerTabIntent(timerId: Int): PendingIntent {
val intent = getLaunchIntent() ?: Intent(this, SplashActivity::class.java)
intent.putExtra(OPEN_TAB, TAB_TIMER)
intent.putExtra(TIMER_ID, timerId)

View file

@ -44,7 +44,7 @@ class TimerFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
view = (inflater.inflate(R.layout.fragment_timer, container, false) as ViewGroup).apply {
timerAdapter = TimerAdapter(requireActivity() as SimpleActivity, ::refreshTimers, ::openEditTimer)
timerAdapter = TimerAdapter(requireActivity() as SimpleActivity, timers_list, ::refreshTimers, ::openEditTimer)
storeStateVariables()
@ -105,7 +105,7 @@ class TimerFragment : Fragment() {
currentEditAlarmDialog?.updateAlarmSound(alarmSound)
}
fun updatePosition(timerId: Long) {
fun updatePosition(timerId: Int) {
activity?.timerHelper?.getTimers { timers ->
val position = timers.indexOfFirst { it.id == timerId }
if (position != INVALID_POSITION) {

View file

@ -47,7 +47,7 @@ const val TAB_ALARM = 1
const val TAB_STOPWATCH = 2
const val TAB_TIMER = 3
const val TIMER_ID = "timer_id"
const val INVALID_TIMER_ID = -1L
const val INVALID_TIMER_ID = -1
// stopwatch sorting
const val SORT_BY_LAP = 1

View file

@ -14,7 +14,7 @@ class TimerHelper(val context: Context) {
}
}
fun getTimer(timerId: Long, callback: (timer: Timer) -> Unit) {
fun getTimer(timerId: Int, callback: (timer: Timer) -> Unit) {
ensureBackgroundThread {
callback.invoke(timerDao.getTimer(timerId))
}
@ -27,10 +27,17 @@ class TimerHelper(val context: Context) {
}
}
fun deleteTimer(id: Long, callback: () -> Unit = {}) {
fun deleteTimer(id: Int, callback: () -> Unit = {}) {
ensureBackgroundThread {
timerDao.deleteTimer(id)
callback.invoke()
}
}
fun deleteTimers(timers: List<Timer>, callback: () -> Unit = {}) {
ensureBackgroundThread {
timerDao.deleteTimers(timers)
callback.invoke()
}
}
}

View file

@ -1,9 +1,6 @@
package com.simplemobiletools.clock.interfaces
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.*
import com.simplemobiletools.clock.models.Timer
@Dao
@ -13,11 +10,14 @@ interface TimerDao {
fun getTimers(): List<Timer>
@Query("SELECT * FROM timers WHERE id=:id")
fun getTimer(id: Long): Timer
fun getTimer(id: Int): Timer
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertOrUpdateTimer(timer: Timer): Long
@Query("DELETE FROM timers WHERE id=:id")
fun deleteTimer(id: Long)
fun deleteTimer(id: Int)
@Delete
fun deleteTimers(list: List<Timer>)
}

View file

@ -5,7 +5,7 @@ import androidx.room.PrimaryKey
@Entity(tableName = "timers")
data class Timer(
@PrimaryKey(autoGenerate = true) var id: Long?,
@PrimaryKey(autoGenerate = true) var id: Int?,
var seconds: Int,
val state: TimerState,
var vibrate: Boolean,

View file

@ -1,9 +1,9 @@
package com.simplemobiletools.clock.models
sealed class TimerEvent(open val timerId: Long) {
data class Reset(override val timerId: Long, val duration: Long) : TimerEvent(timerId)
data class Start(override val timerId: Long, val duration: Long) : TimerEvent(timerId)
data class Pause(override val timerId: Long, val duration: Long) : TimerEvent(timerId)
data class Finish(override val timerId: Long, val duration: Long) : TimerEvent(timerId)
data class Refresh(override val timerId: Long) : TimerEvent(timerId)
sealed class TimerEvent(open val timerId: Int) {
data class Reset(override val timerId: Int, val duration: Long) : TimerEvent(timerId)
data class Start(override val timerId: Int, val duration: Long) : TimerEvent(timerId)
data class Pause(override val timerId: Int, val duration: Long) : TimerEvent(timerId)
data class Finish(override val timerId: Int, val duration: Long) : TimerEvent(timerId)
data class Refresh(override val timerId: Int) : TimerEvent(timerId)
}

View file

@ -82,7 +82,7 @@ class TimerService : Service() {
}
@TargetApi(Build.VERSION_CODES.O)
private fun notification(title: String, contentText: String, firstRunningTimerId: Long): Notification {
private fun notification(title: String, contentText: String, firstRunningTimerId: Int): Notification {
val channelId = "simple_alarm_timer"
val label = getString(R.string.timer)
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

View file

@ -2,6 +2,7 @@
<FrameLayout 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:id="@+id/timer_frame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"