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 { 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 'com.facebook.stetho:stetho:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.shawnlin:number-picker:2.4.6' implementation 'com.shawnlin:number-picker:2.4.6'

View file

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

View file

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

View file

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

View file

@ -1,28 +1,32 @@
package com.simplemobiletools.clock.adapters package com.simplemobiletools.clock.adapters
import android.graphics.Color import android.graphics.Color
import android.view.LayoutInflater import android.view.Menu
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
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.dialogs.MyTimePickerDialogDialog 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.Timer
import com.simplemobiletools.clock.models.TimerEvent import com.simplemobiletools.clock.models.TimerEvent
import com.simplemobiletools.clock.models.TimerState import com.simplemobiletools.clock.models.TimerState
import com.simplemobiletools.commons.adapters.MyRecyclerViewListAdapter
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.views.MyRecyclerView
import kotlinx.android.synthetic.main.item_timer.view.* import kotlinx.android.synthetic.main.item_timer.view.*
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
class TimerAdapter( class TimerAdapter(
private val activity: SimpleActivity, private val simpleActivity: SimpleActivity,
private val onRefresh: () -> Unit, recyclerView: MyRecyclerView,
private val onClick: (Timer) -> Unit, onRefresh: () -> Unit,
) : ListAdapter<Timer, TimerAdapter.TimerViewHolder>(diffUtil) { private val onItemClick: (Timer) -> Unit,
) : MyRecyclerViewListAdapter<Timer>(simpleActivity, recyclerView, diffUtil, null, onItemClick, onRefresh) {
companion object { companion object {
private val diffUtil = object : DiffUtil.ItemCallback<Timer>() { private val diffUtil = object : DiffUtil.ItemCallback<Timer>() {
@ -36,101 +40,130 @@ class TimerAdapter(
} }
} }
private val config = activity.config init {
private var textColor = config.textColor setupDragListener(true)
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))
} }
override fun onBindViewHolder(holder: TimerViewHolder, position: Int) { override fun getActionMenuId() = R.menu.cab_alarms
holder.bind(getItem(position))
}
fun updateTextColor(textColor: Int) { override fun prepareActionMode(menu: Menu) {}
this.textColor = textColor
onRefresh.invoke()
}
inner class TimerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { override fun actionItemPressed(id: Int) {
if (selectedKeys.isEmpty()) {
fun bind(timer: Timer) { return
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)
}
}
} }
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 val resetPossible = state is TimerState.Running || state is TimerState.Paused || state is TimerState.Finished
itemView.timer_reset.beInvisibleIf(!resetPossible) timer_reset.beInvisibleIf(!resetPossible)
itemView.timer_delete.beInvisibleIf(!(!resetPossible && itemCount > 1)) 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 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 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) { private fun changeDuration(timer: Timer) {
MyTimePickerDialogDialog(activity, timer.seconds) { seconds -> MyTimePickerDialogDialog(simpleActivity, timer.seconds) { seconds ->
val timerSeconds = if (seconds <= 0) 10 else seconds val timerSeconds = if (seconds <= 0) 10 else seconds
updateTimer(timer.copy(seconds = timerSeconds)) updateTimer(timer.copy(seconds = timerSeconds))
} }
} }
private fun updateTimer(timer: Timer, refresh: Boolean = true) { private fun updateTimer(timer: Timer, refresh: Boolean = true) {
activity.timerHelper.insertOrUpdateTimer(timer) { simpleActivity.timerHelper.insertOrUpdateTimer(timer) {
if (refresh) { if (refresh) {
onRefresh.invoke() onRefresh.invoke()
} }
@ -139,7 +172,7 @@ class TimerAdapter(
private fun stopTimer(timer: Timer) { private fun stopTimer(timer: Timer) {
EventBus.getDefault().post(TimerEvent.Reset(timer.id!!, timer.seconds.secondsToMillis)) 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) (fragments[TAB_TIMER] as? TimerFragment)?.updateAlarmSound(alarmSound)
} }
fun updateTimerPosition(timerId: Long) { fun updateTimerPosition(timerId: Int) {
(fragments[TAB_TIMER] as? TimerFragment)?.updatePosition(timerId) (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) 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) val intent = getLaunchIntent() ?: Intent(this, SplashActivity::class.java)
intent.putExtra(OPEN_TAB, TAB_TIMER) intent.putExtra(OPEN_TAB, TAB_TIMER)
intent.putExtra(TIMER_ID, timerId) intent.putExtra(TIMER_ID, timerId)

View file

@ -44,7 +44,7 @@ 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 {
timerAdapter = TimerAdapter(requireActivity() as SimpleActivity, ::refreshTimers, ::openEditTimer) timerAdapter = TimerAdapter(requireActivity() as SimpleActivity, timers_list, ::refreshTimers, ::openEditTimer)
storeStateVariables() storeStateVariables()
@ -105,7 +105,7 @@ class TimerFragment : Fragment() {
currentEditAlarmDialog?.updateAlarmSound(alarmSound) currentEditAlarmDialog?.updateAlarmSound(alarmSound)
} }
fun updatePosition(timerId: Long) { fun updatePosition(timerId: Int) {
activity?.timerHelper?.getTimers { timers -> activity?.timerHelper?.getTimers { timers ->
val position = timers.indexOfFirst { it.id == timerId } val position = timers.indexOfFirst { it.id == timerId }
if (position != INVALID_POSITION) { if (position != INVALID_POSITION) {

View file

@ -47,7 +47,7 @@ const val TAB_ALARM = 1
const val TAB_STOPWATCH = 2 const val TAB_STOPWATCH = 2
const val TAB_TIMER = 3 const val TAB_TIMER = 3
const val TIMER_ID = "timer_id" const val TIMER_ID = "timer_id"
const val INVALID_TIMER_ID = -1L const val INVALID_TIMER_ID = -1
// stopwatch sorting // stopwatch sorting
const val SORT_BY_LAP = 1 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 { ensureBackgroundThread {
callback.invoke(timerDao.getTimer(timerId)) 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 { ensureBackgroundThread {
timerDao.deleteTimer(id) timerDao.deleteTimer(id)
callback.invoke() 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 package com.simplemobiletools.clock.interfaces
import androidx.room.Dao import androidx.room.*
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.simplemobiletools.clock.models.Timer import com.simplemobiletools.clock.models.Timer
@Dao @Dao
@ -13,11 +10,14 @@ interface TimerDao {
fun getTimers(): List<Timer> fun getTimers(): List<Timer>
@Query("SELECT * FROM timers WHERE id=:id") @Query("SELECT * FROM timers WHERE id=:id")
fun getTimer(id: Long): Timer fun getTimer(id: Int): Timer
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertOrUpdateTimer(timer: Timer): Long fun insertOrUpdateTimer(timer: Timer): Long
@Query("DELETE FROM timers WHERE id=:id") @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") @Entity(tableName = "timers")
data class Timer( data class Timer(
@PrimaryKey(autoGenerate = true) var id: Long?, @PrimaryKey(autoGenerate = true) var id: Int?,
var seconds: Int, var seconds: Int,
val state: TimerState, val state: TimerState,
var vibrate: Boolean, var vibrate: Boolean,

View file

@ -1,9 +1,9 @@
package com.simplemobiletools.clock.models package com.simplemobiletools.clock.models
sealed class TimerEvent(open val timerId: Long) { sealed class TimerEvent(open val timerId: Int) {
data class Reset(override val timerId: Long, val duration: Long) : TimerEvent(timerId) data class Reset(override val timerId: Int, val duration: Long) : TimerEvent(timerId)
data class Start(override val timerId: Long, val duration: Long) : TimerEvent(timerId) data class Start(override val timerId: Int, val duration: Long) : TimerEvent(timerId)
data class Pause(override val timerId: Long, val duration: Long) : TimerEvent(timerId) data class Pause(override val timerId: Int, val duration: Long) : TimerEvent(timerId)
data class Finish(override val timerId: Long, val duration: Long) : TimerEvent(timerId) data class Finish(override val timerId: Int, val duration: Long) : TimerEvent(timerId)
data class Refresh(override val timerId: 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) @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 channelId = "simple_alarm_timer"
val label = getString(R.string.timer) val label = getString(R.string.timer)
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

View file

@ -2,6 +2,7 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/timer_frame"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?selectableItemBackground" android:background="?selectableItemBackground"