resolve of PR

This commit is contained in:
Julien BOUTICOURT
2025-03-06 20:31:37 +01:00
20 changed files with 587 additions and 121 deletions

View File

@@ -13,7 +13,10 @@ import org.fossify.clock.BuildConfig
import org.fossify.clock.R
import org.fossify.clock.adapters.ViewPagerAdapter
import org.fossify.clock.databinding.ActivityMainBinding
import org.fossify.clock.extensions.*
import org.fossify.clock.extensions.config
import org.fossify.clock.extensions.getEnabledAlarms
import org.fossify.clock.extensions.rescheduleEnabledAlarms
import org.fossify.clock.extensions.updateWidgets
import org.fossify.clock.helpers.*
import org.fossify.commons.databinding.BottomTablayoutItemBinding
import org.fossify.commons.extensions.*
@@ -127,7 +130,11 @@ class MainActivity : SimpleActivity() {
private fun setupOptionsMenu() {
binding.mainToolbar.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.sort -> getViewPagerAdapter()?.showAlarmSortDialog()
R.id.sort -> when (binding.viewPager.currentItem) {
TAB_ALARM_INDEX -> getViewPagerAdapter()?.showAlarmSortDialog()
TAB_TIMER_INDEX -> getViewPagerAdapter()?.showTimerSortDialog()
}
R.id.more_apps_from_us -> launchMoreAppsFromUsIntent()
R.id.settings -> launchSettings()
R.id.about -> launchAbout()
@@ -139,7 +146,7 @@ class MainActivity : SimpleActivity() {
private fun refreshMenuItems() {
binding.mainToolbar.menu.apply {
findItem(R.id.sort).isVisible = binding.viewPager.currentItem == getTabIndex(TAB_ALARM)
findItem(R.id.sort).isVisible = binding.viewPager.currentItem == getTabIndex(TAB_ALARM) || binding.viewPager.currentItem == getTabIndex(TAB_TIMER)
findItem(R.id.more_apps_from_us).isVisible = !resources.getBoolean(org.fossify.commons.R.bool.hide_google_relations)
}
}

View File

@@ -1,8 +1,12 @@
package org.fossify.clock.adapters
import android.annotation.SuppressLint
import android.view.Menu
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import org.fossify.clock.R
import org.fossify.clock.activities.SimpleActivity
import org.fossify.clock.databinding.ItemAlarmBinding
@@ -14,17 +18,32 @@ import org.fossify.clock.interfaces.ToggleAlarmInterface
import org.fossify.clock.models.Alarm
import org.fossify.commons.adapters.MyRecyclerViewAdapter
import org.fossify.commons.dialogs.ConfirmationDialog
import org.fossify.commons.extensions.applyColorFilter
import org.fossify.commons.extensions.beVisibleIf
import org.fossify.commons.extensions.toast
import org.fossify.commons.helpers.SORT_BY_CUSTOM
import org.fossify.commons.interfaces.ItemMoveCallback
import org.fossify.commons.interfaces.ItemTouchHelperContract
import org.fossify.commons.interfaces.StartReorderDragListener
import org.fossify.commons.views.MyRecyclerView
class AlarmsAdapter(
activity: SimpleActivity, var alarms: ArrayList<Alarm>, val toggleAlarmInterface: ToggleAlarmInterface,
recyclerView: MyRecyclerView, itemClick: (Any) -> Unit,
) : MyRecyclerViewAdapter(activity, recyclerView, itemClick) {
) : MyRecyclerViewAdapter(activity, recyclerView, itemClick), ItemTouchHelperContract {
private var startReorderDragListener: StartReorderDragListener
init {
setupDragListener(true)
val touchHelper = ItemTouchHelper(ItemMoveCallback(this))
touchHelper.attachToRecyclerView(recyclerView)
startReorderDragListener = object : StartReorderDragListener {
override fun requestDrag(viewHolder: RecyclerView.ViewHolder) {
touchHelper.startDrag(viewHolder)
}
}
}
override fun getActionMenuId() = R.menu.cab_alarms
@@ -49,9 +68,19 @@ class AlarmsAdapter(
override fun getItemKeyPosition(key: Int) = alarms.indexOfFirst { it.id == key }
override fun onActionModeCreated() {}
@SuppressLint("NotifyDataSetChanged")
override fun onActionModeCreated() {
notifyDataSetChanged()
}
override fun onActionModeDestroyed() {}
@SuppressLint("NotifyDataSetChanged")
override fun onActionModeDestroyed() {
notifyDataSetChanged()
}
override fun onRowClear(myViewHolder: ViewHolder?) {}
override fun onRowSelected(myViewHolder: ViewHolder?) {}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return createViewHolder(ItemAlarmBinding.inflate(layoutInflater, parent, false).root)
@@ -59,14 +88,15 @@ class AlarmsAdapter(
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val alarm = alarms[position]
holder.bindView(alarm, true, true) { itemView, layoutPosition ->
setupView(itemView, alarm)
holder.bindView(alarm, true, true) { itemView, _ ->
setupView(itemView, alarm, holder)
}
bindViewHolder(holder)
}
override fun getItemCount() = alarms.size
@SuppressLint("NotifyDataSetChanged")
fun updateItems(newItems: ArrayList<Alarm>) {
alarms = newItems
notifyDataSetChanged()
@@ -87,10 +117,19 @@ class AlarmsAdapter(
private fun getSelectedItems() = alarms.filter { selectedKeys.contains(it.id) } as ArrayList<Alarm>
private fun setupView(view: View, alarm: Alarm) {
@SuppressLint("ClickableViewAccessibility")
private fun setupView(view: View, alarm: Alarm, holder: ViewHolder) {
val isSelected = selectedKeys.contains(alarm.id)
ItemAlarmBinding.bind(view).apply {
alarmHolder.isSelected = isSelected
alarmDragHandle.beVisibleIf(selectedKeys.isNotEmpty())
alarmDragHandle.applyColorFilter(textColor)
alarmDragHandle.setOnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
startReorderDragListener.requestDrag(holder)
}
false
}
alarmTime.text = activity.getFormattedTime(alarm.timeInMinutes * 60, false, true)
alarmTime.setTextColor(textColor)
@@ -137,4 +176,19 @@ class AlarmsAdapter(
}
}
}
override fun onRowMoved(fromPosition: Int, toPosition: Int) {
alarms.swap(fromPosition, toPosition)
notifyItemMoved(fromPosition, toPosition)
saveAlarmsCustomOrder(alarms)
if (activity.config.alarmSort != SORT_BY_CUSTOM) {
activity.config.alarmSort = SORT_BY_CUSTOM
}
}
private fun saveAlarmsCustomOrder(alarms: ArrayList<Alarm>) {
val alarmsCustomSortingIds = alarms.map { it.id }
activity.config.alarmsCustomSorting = alarmsCustomSortingIds.joinToString { it.toString() }
}
}

View File

@@ -1,5 +1,6 @@
package org.fossify.clock.adapters
import android.annotation.SuppressLint
import android.view.Menu
import android.view.View
import android.view.ViewGroup
@@ -52,6 +53,7 @@ class StopwatchAdapter(activity: SimpleActivity, var laps: ArrayList<Lap>, recyc
override fun getItemCount() = laps.size
@SuppressLint("NotifyDataSetChanged")
fun updateItems(newItems: ArrayList<Lap>) {
lastLapId = 0
laps = newItems.clone() as ArrayList<Lap>

View File

@@ -1,5 +1,6 @@
package org.fossify.clock.adapters
import android.annotation.SuppressLint
import android.view.Menu
import android.view.View
import android.view.ViewGroup
@@ -67,12 +68,14 @@ class TimeZonesAdapter(activity: SimpleActivity, var timeZones: ArrayList<MyTime
override fun getItemCount() = timeZones.size
@SuppressLint("NotifyDataSetChanged")
fun updateItems(newItems: ArrayList<MyTimeZone>) {
timeZones = newItems
notifyDataSetChanged()
finishActMode()
}
@SuppressLint("NotifyDataSetChanged")
fun updateTimes() {
notifyDataSetChanged()
}

View File

@@ -1,22 +1,41 @@
package org.fossify.clock.adapters
import android.annotation.SuppressLint
import android.view.Menu
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import me.grantland.widget.AutofitHelper
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import org.fossify.clock.R
import org.fossify.clock.activities.SimpleActivity
import org.fossify.clock.databinding.ItemTimerBinding
import org.fossify.clock.extensions.config
import org.fossify.clock.extensions.getFormattedDuration
import org.fossify.clock.extensions.hideTimerNotification
import org.fossify.clock.extensions.secondsToMillis
import org.fossify.clock.extensions.swap
import org.fossify.clock.models.Timer
import org.fossify.clock.models.TimerEvent
import org.fossify.clock.models.TimerState
import org.fossify.clock.models.TimerState.Finished
import org.fossify.clock.models.TimerState.Idle
import org.fossify.clock.models.TimerState.Paused
import org.fossify.clock.models.TimerState.Running
import org.fossify.commons.adapters.MyRecyclerViewAdapter
import org.fossify.commons.adapters.MyRecyclerViewListAdapter
import org.fossify.commons.dialogs.PermissionRequiredDialog
import org.fossify.commons.extensions.*
import org.fossify.commons.extensions.adjustAlpha
import org.fossify.commons.extensions.applyColorFilter
import org.fossify.commons.extensions.beInvisibleIf
import org.fossify.commons.extensions.beVisibleIf
import org.fossify.commons.extensions.getColoredDrawableWithColor
import org.fossify.commons.extensions.getFormattedDuration
import org.fossify.commons.extensions.openNotificationSettings
import org.fossify.commons.helpers.SORT_BY_CUSTOM
import org.fossify.commons.interfaces.ItemMoveCallback
import org.fossify.commons.interfaces.ItemTouchHelperContract
import org.fossify.commons.interfaces.StartReorderDragListener
import org.fossify.commons.views.MyRecyclerView
import org.greenrobot.eventbus.EventBus
@@ -25,7 +44,15 @@ class TimerAdapter(
recyclerView: MyRecyclerView,
onRefresh: () -> Unit,
onItemClick: (Timer) -> Unit,
) : MyRecyclerViewListAdapter<Timer>(simpleActivity, recyclerView, diffUtil, onItemClick, onRefresh) {
) : MyRecyclerViewListAdapter<Timer>(
activity = simpleActivity,
recyclerView = recyclerView,
diffUtil = diffUtil,
itemClick = onItemClick,
onRefresh = onRefresh
), ItemTouchHelperContract {
private var startReorderDragListener: StartReorderDragListener
companion object {
private val diffUtil = object : DiffUtil.ItemCallback<Timer>() {
@@ -41,6 +68,32 @@ class TimerAdapter(
init {
setupDragListener(true)
setHasStableIds(true)
val touchHelper = ItemTouchHelper(ItemMoveCallback(this))
touchHelper.attachToRecyclerView(recyclerView)
startReorderDragListener = object : StartReorderDragListener {
override fun requestDrag(viewHolder: RecyclerView.ViewHolder) {
touchHelper.startDrag(viewHolder)
}
}
}
override fun getItemId(position: Int): Long {
return getItem(position).id!!.toLong()
}
override fun submitList(list: MutableList<Timer>?, commitCallback: Runnable?) {
val layoutManager = recyclerView.layoutManager!!
val recyclerViewState = layoutManager.onSaveInstanceState()
super.submitList(list) {
layoutManager.onRestoreInstanceState(recyclerViewState)
commitCallback?.run()
}
}
override fun submitList(list: MutableList<Timer>?) {
submitList(list, null)
}
override fun getActionMenuId() = R.menu.cab_alarms
@@ -74,17 +127,31 @@ class TimerAdapter(
return position
}
override fun onActionModeCreated() {}
@SuppressLint("NotifyDataSetChanged")
override fun onActionModeCreated() {
notifyDataSetChanged()
}
override fun onActionModeDestroyed() {}
@SuppressLint("NotifyDataSetChanged")
override fun onActionModeDestroyed() {
notifyDataSetChanged()
}
override fun onRowClear(myViewHolder: MyRecyclerViewAdapter.ViewHolder?) {}
override fun onRowSelected(myViewHolder: MyRecyclerViewAdapter.ViewHolder?) {}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return createViewHolder(ItemTimerBinding.inflate(layoutInflater, parent, false).root)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bindView(getItem(position), true, true) { itemView, _ ->
setupView(itemView, getItem(position))
holder.bindView(
item = getItem(position),
allowSingleClick = true,
allowLongClick = true
) { itemView, _ ->
setupView(view = itemView, timer = getItem(position), holder = holder)
}
bindViewHolder(holder)
}
@@ -98,22 +165,31 @@ class TimerAdapter(
timersToRemove.forEach(::deleteTimer)
}
private fun setupView(view: View, timer: Timer) {
@SuppressLint("ClickableViewAccessibility")
private fun setupView(view: View, timer: Timer, holder: ViewHolder) {
ItemTimerBinding.bind(view).apply {
val isSelected = selectedKeys.contains(timer.id)
timerFrame.isSelected = isSelected
timerDragHandle.beVisibleIf(selectedKeys.isNotEmpty())
timerDragHandle.applyColorFilter(textColor)
timerDragHandle.setOnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
startReorderDragListener.requestDrag(holder)
}
false
}
timerLabel.setTextColor(textColor)
timerLabel.setHintTextColor(textColor.adjustAlpha(0.7f))
timerLabel.text = timer.label
timerLabel.beVisibleIf(timer.label.isNotEmpty())
AutofitHelper.create(timerTime)
timerTime.setTextColor(textColor)
timerTime.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()
is Finished -> 0.getFormattedDuration()
is Idle -> timer.seconds.getFormattedDuration()
is Paused -> timer.state.tick.getFormattedDuration()
is Running -> timer.state.tick.getFormattedDuration()
}
timerReset.applyColorFilter(textColor)
@@ -123,32 +199,52 @@ class TimerAdapter(
timerPlayPause.applyColorFilter(textColor)
timerPlayPause.setOnClickListener {
(activity as SimpleActivity).handleNotificationPermission { granted ->
if (granted) {
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))
}
} else {
PermissionRequiredDialog(
activity,
org.fossify.commons.R.string.allow_notifications_reminders,
{ activity.openNotificationSettings() })
}
}
toggleTimer(timer)
}
val state = timer.state
val resetPossible = state is TimerState.Running || state is TimerState.Paused || state is TimerState.Finished
val resetPossible = state is Running || state is Paused || state is Finished
timerReset.beInvisibleIf(!resetPossible)
val drawableId = if (state is TimerState.Running) {
org.fossify.commons.R.drawable.ic_pause_vector
timerPlayPause.setImageDrawable(
simpleActivity.resources.getColoredDrawableWithColor(
drawableId = if (state is Running) {
org.fossify.commons.R.drawable.ic_pause_vector
} else {
org.fossify.commons.R.drawable.ic_play_vector
},
color = textColor
)
)
}
}
private fun toggleTimer(timer: Timer) {
(activity as SimpleActivity).handleNotificationPermission { granted ->
if (granted) {
when (val state = timer.state) {
is Idle -> EventBus.getDefault().post(
TimerEvent.Start(timer.id!!, timer.seconds.secondsToMillis)
)
is Paused -> EventBus.getDefault().post(
TimerEvent.Start(timer.id!!, state.tick)
)
is Running -> EventBus.getDefault().post(
TimerEvent.Pause(timer.id!!, state.tick)
)
is Finished -> EventBus.getDefault().post(
TimerEvent.Start(timer.id!!, timer.seconds.secondsToMillis)
)
}
} else {
org.fossify.commons.R.drawable.ic_play_vector
PermissionRequiredDialog(
activity = activity,
textId = org.fossify.commons.R.string.allow_notifications_reminders,
positiveActionCallback = { activity.openNotificationSettings() }
)
}
timerPlayPause.setImageDrawable(simpleActivity.resources.getColoredDrawableWithColor(drawableId, textColor))
}
}
@@ -161,4 +257,20 @@ class TimerAdapter(
EventBus.getDefault().post(TimerEvent.Delete(timer.id!!))
simpleActivity.hideTimerNotification(timer.id!!)
}
override fun onRowMoved(fromPosition: Int, toPosition: Int) {
val timers = currentList.toMutableList()
timers.swap(fromPosition, toPosition)
submitList(timers)
saveAlarmsCustomOrder(ArrayList(timers))
if (simpleActivity.config.timerSort != SORT_BY_CUSTOM) {
simpleActivity.config.timerSort = SORT_BY_CUSTOM
}
}
private fun saveAlarmsCustomOrder(alarms: ArrayList<Timer>) {
val timersCustomSortingIds = alarms.map { it.id }
simpleActivity.config.timersCustomSorting =
timersCustomSortingIds.joinToString { it.toString() }
}
}

View File

@@ -45,6 +45,10 @@ class ViewPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
(fragments[TAB_ALARM_INDEX] as? AlarmFragment)?.showSortingDialog()
}
fun showTimerSortDialog() {
(fragments[TAB_TIMER_INDEX] as? TimerFragment)?.showSortingDialog()
}
fun updateClockTabAlarm() {
(fragments[TAB_CLOCK_INDEX] as? ClockFragment)?.updateAlarm()
}

View File

@@ -9,12 +9,14 @@ import org.fossify.clock.helpers.SORT_BY_DATE_AND_TIME
import org.fossify.commons.activities.BaseSimpleActivity
import org.fossify.commons.extensions.getAlertDialogBuilder
import org.fossify.commons.extensions.setupDialogStuff
import org.fossify.commons.helpers.SORT_BY_CUSTOM
class ChangeAlarmSortDialog(val activity: BaseSimpleActivity, val callback: () -> Unit) {
private val binding = DialogChangeAlarmSortBinding.inflate(activity.layoutInflater).apply {
val activeRadioButton = when (activity.config.alarmSort) {
SORT_BY_ALARM_TIME -> sortingDialogRadioAlarmTime
SORT_BY_DATE_AND_TIME -> sortingDialogRadioDayAndTime
SORT_BY_CUSTOM -> sortingDialogRadioCustom
else -> sortingDialogRadioCreationOrder
}
activeRadioButton.isChecked = true
@@ -33,6 +35,7 @@ class ChangeAlarmSortDialog(val activity: BaseSimpleActivity, val callback: () -
val sort = when (binding.sortingDialogRadioSorting.checkedRadioButtonId) {
R.id.sorting_dialog_radio_alarm_time -> SORT_BY_ALARM_TIME
R.id.sorting_dialog_radio_day_and_time -> SORT_BY_DATE_AND_TIME
R.id.sorting_dialog_radio_custom -> SORT_BY_CUSTOM
else -> SORT_BY_CREATION_ORDER
}

View File

@@ -0,0 +1,42 @@
package org.fossify.clock.dialogs
import org.fossify.clock.R
import org.fossify.clock.databinding.DialogChangeTimerSortBinding
import org.fossify.clock.extensions.config
import org.fossify.clock.helpers.SORT_BY_CREATION_ORDER
import org.fossify.clock.helpers.SORT_BY_TIMER_DURATION
import org.fossify.commons.activities.BaseSimpleActivity
import org.fossify.commons.extensions.getAlertDialogBuilder
import org.fossify.commons.extensions.setupDialogStuff
import org.fossify.commons.helpers.SORT_BY_CUSTOM
class ChangeTimerSortDialog(val activity: BaseSimpleActivity, val callback: () -> Unit) {
private val binding = DialogChangeTimerSortBinding.inflate(activity.layoutInflater).apply {
val activeRadioButton = when (activity.config.timerSort) {
SORT_BY_TIMER_DURATION -> sortingDialogRadioTimerDuration
SORT_BY_CUSTOM -> sortingDialogRadioCustom
else -> sortingDialogRadioCreationOrder
}
activeRadioButton.isChecked = true
}
init {
activity.getAlertDialogBuilder()
.setPositiveButton(org.fossify.commons.R.string.ok) { _, _ -> dialogConfirmed() }
.setNegativeButton(org.fossify.commons.R.string.cancel, null)
.apply {
activity.setupDialogStuff(binding.root, this, org.fossify.commons.R.string.sort_by)
}
}
private fun dialogConfirmed() {
val sort = when (binding.sortingDialogRadioSorting.checkedRadioButtonId) {
R.id.sorting_dialog_radio_timer_duration -> SORT_BY_TIMER_DURATION
R.id.sorting_dialog_radio_custom -> SORT_BY_CUSTOM
else -> SORT_BY_CREATION_ORDER
}
activity.config.timerSort = sort
callback()
}
}

View File

@@ -0,0 +1,14 @@
package org.fossify.clock.extensions
@Deprecated(
message = "Use the `move` extension function from commons",
replaceWith = ReplaceWith(
expression = "this.move(index1, index2)",
imports = ["org.fossify.commons.extensions.move"]
)
)
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
this[index1] = this[index2].also {
this[index2] = this[index1]
}
}

View File

@@ -20,6 +20,7 @@ import org.fossify.commons.extensions.getProperBackgroundColor
import org.fossify.commons.extensions.getProperTextColor
import org.fossify.commons.extensions.toast
import org.fossify.commons.extensions.updateTextColors
import org.fossify.commons.helpers.SORT_BY_CUSTOM
import org.fossify.commons.helpers.SORT_BY_DATE_CREATED
import org.fossify.commons.helpers.ensureBackgroundThread
import org.fossify.commons.models.AlarmSound
@@ -84,6 +85,25 @@ class AlarmFragment : Fragment(), ToggleAlarmInterface {
}.thenBy {
it.timeInMinutes
})
SORT_BY_CUSTOM -> {
val customAlarmsSortOrderString = activity?.config?.alarmsCustomSorting
if (customAlarmsSortOrderString == "") {
alarms.sortBy { it.id }
} else {
val customAlarmsSortOrder: List<Int> = customAlarmsSortOrderString?.split(", ")?.map { it.toInt() }!!
val alarmsIdValueMap = alarms.associateBy { it.id }
val sortedAlarms: ArrayList<Alarm> = ArrayList()
customAlarmsSortOrder.map { id ->
if (alarmsIdValueMap[id] != null) {
sortedAlarms.add(alarmsIdValueMap[id] as Alarm)
}
}
alarms = (sortedAlarms + alarms.filter { it !in sortedAlarms }) as ArrayList<Alarm>
}
}
}
context?.getEnabledAlarms { enabledAlarms ->
if (enabledAlarms.isNullOrEmpty()) {

View File

@@ -10,24 +10,27 @@ import androidx.fragment.app.Fragment
import org.fossify.clock.activities.SimpleActivity
import org.fossify.clock.adapters.TimerAdapter
import org.fossify.clock.databinding.FragmentTimerBinding
import org.fossify.clock.dialogs.ChangeTimerSortDialog
import org.fossify.clock.dialogs.EditTimerDialog
import org.fossify.clock.extensions.config
import org.fossify.clock.extensions.createNewTimer
import org.fossify.clock.extensions.timerHelper
import org.fossify.clock.helpers.DisabledItemChangeAnimator
import org.fossify.clock.helpers.SORT_BY_TIMER_DURATION
import org.fossify.clock.models.Timer
import org.fossify.clock.models.TimerEvent
import org.fossify.commons.extensions.getProperBackgroundColor
import org.fossify.commons.extensions.getProperTextColor
import org.fossify.commons.extensions.hideKeyboard
import org.fossify.commons.extensions.updateTextColors
import org.fossify.commons.helpers.SORT_BY_CUSTOM
import org.fossify.commons.helpers.SORT_BY_DATE_CREATED
import org.fossify.commons.models.AlarmSound
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class TimerFragment : Fragment() {
private val INVALID_POSITION = -1
private lateinit var binding: FragmentTimerBinding
private lateinit var timerAdapter: TimerAdapter
private var timerPositionToScrollTo = INVALID_POSITION
@@ -43,7 +46,11 @@ class TimerFragment : Fragment() {
super.onDestroy()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
binding = FragmentTimerBinding.inflate(inflater, container, false).apply {
timersList.itemAnimator = DisabledItemChangeAnimator()
timerAdd.setOnClickListener {
@@ -73,7 +80,12 @@ class TimerFragment : Fragment() {
timerAdapter.updateBackgroundColor(requireContext().getProperBackgroundColor())
timerAdapter.updateTextColor(requireContext().getProperTextColor())
} else {
timerAdapter = TimerAdapter(requireActivity() as SimpleActivity, binding.timersList, ::refreshTimers, ::openEditTimer)
timerAdapter = TimerAdapter(
simpleActivity = requireActivity() as SimpleActivity,
recyclerView = binding.timersList,
onRefresh = ::refreshTimers,
onItemClick = ::openEditTimer
)
binding.timersList.adapter = timerAdapter
}
}
@@ -85,16 +97,67 @@ class TimerFragment : Fragment() {
refreshTimers()
}
private fun refreshTimers(scrollToLatest: Boolean = false) {
fun showSortingDialog() {
ChangeTimerSortDialog(activity as SimpleActivity) {
refreshTimers(
animate = false // disable sorting animations for now.
)
}
}
private fun getSortedTimers(callback: (List<Timer>) -> Unit) {
activity?.timerHelper?.getTimers { timers ->
val sortedTimers = when (requireContext().config.timerSort) {
SORT_BY_TIMER_DURATION -> timers.sortedBy { it.seconds }
SORT_BY_DATE_CREATED -> timers.sortedBy { it.id }
SORT_BY_CUSTOM -> {
val customTimersSortOrderString = activity?.config?.timersCustomSorting
if (customTimersSortOrderString == "") {
timers.sortedBy { it.id }
} else {
val customTimersSortOrder =
customTimersSortOrderString?.split(", ")?.map { it.toInt() }!!
val timersIdValueMap = timers.associateBy { it.id }
val sortedTimers: ArrayList<Timer> = ArrayList()
customTimersSortOrder.map { id ->
if (timersIdValueMap[id] != null) {
sortedTimers.add(timersIdValueMap[id] as Timer)
}
}
(sortedTimers + timers.filter { it !in sortedTimers }) as ArrayList<Timer>
}
}
else -> timers
}
activity?.runOnUiThread {
timerAdapter.submitList(timers) {
callback(sortedTimers)
}
}
}
private fun refreshTimers(animate: Boolean = true) {
getSortedTimers { timers ->
with(binding.timersList) {
val originalAnimator = itemAnimator
if (!animate) {
itemAnimator = null
}
timerAdapter.submitList(timers.toMutableList()) {
view?.post {
if (timerPositionToScrollTo != INVALID_POSITION && timerAdapter.itemCount > timerPositionToScrollTo) {
binding.timersList.scrollToPosition(timerPositionToScrollTo)
if (timerPositionToScrollTo != INVALID_POSITION &&
timerAdapter.itemCount > timerPositionToScrollTo
) {
smoothScrollToPosition(timerPositionToScrollTo)
timerPositionToScrollTo = INVALID_POSITION
} else if (scrollToLatest) {
binding.timersList.scrollToPosition(timers.lastIndex)
}
if (!animate) {
itemAnimator = originalAnimator
}
}
}
@@ -112,15 +175,13 @@ class TimerFragment : Fragment() {
}
fun updatePosition(timerId: Int) {
activity?.timerHelper?.getTimers { timers ->
getSortedTimers { timers ->
val position = timers.indexOfFirst { it.id == timerId }
if (position != INVALID_POSITION) {
activity?.runOnUiThread {
if (timerAdapter.itemCount > position) {
binding.timersList.scrollToPosition(position)
} else {
timerPositionToScrollTo = position
}
if (timerAdapter.itemCount > position) {
binding.timersList.smoothScrollToPosition(position)
} else {
timerPositionToScrollTo = position
}
}
}
@@ -133,3 +194,5 @@ class TimerFragment : Fragment() {
}
}
}
private const val INVALID_POSITION = -1

View File

@@ -59,6 +59,18 @@ class Config(context: Context) : BaseConfig(context) {
get() = prefs.getInt(ALARMS_SORT_BY, SORT_BY_CREATION_ORDER)
set(alarmSort) = prefs.edit().putInt(ALARMS_SORT_BY, alarmSort).apply()
var alarmsCustomSorting: String
get() = prefs.getString(ALARMS_CUSTOM_SORTING, "")!!
set(alarmsCustomSorting) = prefs.edit().putString(ALARMS_CUSTOM_SORTING, alarmsCustomSorting).apply()
var timerSort: Int
get() = prefs.getInt(TIMERS_SORT_BY, SORT_BY_CREATION_ORDER)
set(timerSort) = prefs.edit().putInt(TIMERS_SORT_BY, timerSort).apply()
var timersCustomSorting: String
get() = prefs.getString(TIMERS_CUSTOM_SORTING, "")!!
set(timersCustomSorting) = prefs.edit().putString(TIMERS_CUSTOM_SORTING, timersCustomSorting).apply()
var alarmMaxReminderSecs: Int
get() = prefs.getInt(ALARM_MAX_REMINDER_SECS, DEFAULT_MAX_ALARM_REMINDER_SECS)
set(alarmMaxReminderSecs) = prefs.edit().putInt(ALARM_MAX_REMINDER_SECS, alarmMaxReminderSecs).apply()

View File

@@ -24,6 +24,9 @@ const val ALARM_LAST_CONFIG = "alarm_last_config"
const val TIMER_LAST_CONFIG = "timer_last_config"
const val INCREASE_VOLUME_GRADUALLY = "increase_volume_gradually"
const val ALARMS_SORT_BY = "alarms_sort_by"
const val ALARMS_CUSTOM_SORTING = "alarms_custom_sorting"
const val TIMERS_SORT_BY = "timers_sort_by"
const val TIMERS_CUSTOM_SORTING = "timers_custom_sorting"
const val STOPWATCH_LAPS_SORT_BY = "stopwatch_laps_sort_by"
const val WAS_INITIAL_WIDGET_SET_UP = "was_initial_widget_set_up"
const val DATA_EXPORT_EXTENSION = ".json"
@@ -70,10 +73,11 @@ const val SORT_BY_LAP = 1
const val SORT_BY_LAP_TIME = 2
const val SORT_BY_TOTAL_TIME = 4
// alarm sorting
// alarm and timer sorting
const val SORT_BY_CREATION_ORDER = 0
const val SORT_BY_ALARM_TIME = 1
const val SORT_BY_DATE_AND_TIME = 2
const val SORT_BY_TIMER_DURATION = 3
const val TODAY_BIT = -1
const val TOMORROW_BIT = -2
@@ -188,6 +192,7 @@ fun getAllTimeZones() = arrayListOf(
MyTimeZone(28, "GMT-01:00 Cape Verde", "Atlantic/Cape_Verde"),
MyTimeZone(29, "GMT+00:00 Casablanca", "Africa/Casablanca"),
MyTimeZone(30, "GMT+00:00 Greenwich Mean Time", "Etc/Greenwich"),
MyTimeZone(90, "GMT+00:00 London", "Europe/London"),
MyTimeZone(31, "GMT+01:00 Amsterdam", "Europe/Amsterdam"),
MyTimeZone(32, "GMT+01:00 Belgrade", "Europe/Belgrade"),
MyTimeZone(33, "GMT+01:00 Brussels", "Europe/Brussels"),

View File

@@ -0,0 +1,47 @@
package org.fossify.clock.views
import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import android.util.AttributeSet
import android.util.TypedValue
import android.widget.TextView
import androidx.annotation.RequiresApi
/**
* A simple wrapper TextView that restores the original text size
* when view width is restored.
*/
@SuppressLint("AppCompatCustomView")
@RequiresApi(Build.VERSION_CODES.O)
class AutoFitTextView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0,
) : TextView(context, attrs, defStyle) {
private var originalTextSize: Float = textSize
private var originalWidth: Int = 0
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
if (originalWidth == 0) {
originalWidth = w
}
post {
if (w >= originalWidth && textSize != originalTextSize) {
disableAutoSizing()
setTextSize(TypedValue.COMPLEX_UNIT_PX, originalTextSize)
enableAutoSizing()
}
}
}
private fun disableAutoSizing() {
setAutoSizeTextTypeWithDefaults(AUTO_SIZE_TEXT_TYPE_NONE)
}
private fun enableAutoSizing() {
post { setAutoSizeTextTypeWithDefaults(AUTO_SIZE_TEXT_TYPE_UNIFORM) }
}
}

View File

@@ -37,5 +37,13 @@
android:paddingBottom="@dimen/medium_margin"
android:text="@string/sort_by_day_and_alarm_time" />
<org.fossify.commons.views.MyCompatRadioButton
android:id="@+id/sorting_dialog_radio_custom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/medium_margin"
android:paddingBottom="@dimen/medium_margin"
android:text="@string/custom" />
</RadioGroup>
</ScrollView>

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/sorting_dialog_scrollview"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RadioGroup
android:id="@+id/sorting_dialog_radio_sorting"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/medium_margin"
android:paddingStart="@dimen/activity_margin"
android:paddingTop="@dimen/activity_margin"
android:paddingEnd="@dimen/activity_margin">
<org.fossify.commons.views.MyCompatRadioButton
android:id="@+id/sorting_dialog_radio_creation_order"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/medium_margin"
android:paddingBottom="@dimen/medium_margin"
android:text="@string/sort_by_creation_order" />
<org.fossify.commons.views.MyCompatRadioButton
android:id="@+id/sorting_dialog_radio_timer_duration"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/medium_margin"
android:paddingBottom="@dimen/medium_margin"
android:text="@string/sort_by_timer_duration" />
<org.fossify.commons.views.MyCompatRadioButton
android:id="@+id/sorting_dialog_radio_custom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/medium_margin"
android:paddingBottom="@dimen/medium_margin"
android:text="@string/custom" />
</RadioGroup>
</ScrollView>

View File

@@ -59,7 +59,20 @@
android:layout_height="wrap_content"
android:paddingHorizontal="@dimen/normal_margin"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/alarm_drag_handle"
app:layout_constraintStart_toEndOf="@id/alarm_time"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/alarm_drag_handle"
android:layout_width="@dimen/drag_handle_size"
android:layout_height="@dimen/drag_handle_size"
android:paddingHorizontal="@dimen/medium_margin"
android:src="@drawable/ic_drag_handle_vector"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/alarm_switch"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout 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"
@@ -8,70 +8,83 @@
android:background="?selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:foreground="@drawable/selector">
android:foreground="@drawable/selector"
android:paddingHorizontal="@dimen/activity_margin"
android:paddingVertical="@dimen/normal_margin">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
<org.fossify.clock.views.AutoFitTextView
android:id="@+id/timer_time"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingStart="@dimen/activity_margin"
android:paddingTop="@dimen/medium_margin"
android:paddingBottom="@dimen/activity_margin">
android:autoSizeMaxTextSize="@dimen/timer_text_size"
android:autoSizeMinTextSize="@dimen/extra_big_text_size"
android:autoSizeStepGranularity="2sp"
android:autoSizeTextType="uniform"
android:gravity="center_vertical"
android:includeFontPadding="false"
android:maxLines="1"
android:textSize="@dimen/timer_text_size"
app:layout_constraintBottom_toTopOf="@id/timer_label"
app:layout_constraintEnd_toStartOf="@id/timer_reset"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="01:30:00" />
<TextView
android:id="@+id/timer_time"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:maxLines="1"
android:textSize="@dimen/alarm_text_size"
app:layout_constraintEnd_toStartOf="@id/timer_reset"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="00:00" />
<org.fossify.commons.views.MyTextView
android:id="@+id/timer_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:includeFontPadding="false"
android:maxLines="1"
android:textSize="@dimen/bigger_text_size"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/timer_time"
app:layout_constraintStart_toStartOf="@id/timer_time"
app:layout_constraintTop_toBottomOf="@id/timer_time"
tools:text="Siesta 🥱💤"
tools:visibility="visible" />
<org.fossify.commons.views.MyTextView
android:id="@+id/timer_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:includeFontPadding="false"
android:maxLines="1"
android:textSize="@dimen/bigger_text_size"
app:layout_constraintEnd_toEndOf="@id/timer_time"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/timer_time"
tools:text="Cook rice" />
<ImageView
android:id="@+id/timer_reset"
android:layout_width="@dimen/timer_button_small_size"
android:layout_height="@dimen/timer_button_small_size"
android:layout_marginHorizontal="@dimen/medium_margin"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="@dimen/normal_margin"
android:src="@drawable/ic_reset_vector"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/timer_play_pause"
app:layout_constraintStart_toEndOf="@id/timer_time"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<ImageView
android:id="@+id/timer_reset"
android:layout_width="@dimen/timer_button_small_size"
android:layout_height="@dimen/timer_button_small_size"
android:layout_marginStart="@dimen/medium_margin"
android:layout_marginEnd="@dimen/medium_margin"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="@dimen/normal_margin"
android:src="@drawable/ic_reset_vector"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="@+id/timer_play_pause"
app:layout_constraintEnd_toStartOf="@+id/timer_play_pause"
app:layout_constraintStart_toEndOf="@id/timer_time"
app:layout_constraintTop_toTopOf="@id/timer_play_pause" />
<ImageView
android:id="@+id/timer_play_pause"
android:layout_width="@dimen/timer_button_size"
android:layout_height="@dimen/timer_button_size"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="@dimen/activity_margin"
android:src="@drawable/ic_play_vector"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/timer_drag_handle"
app:layout_constraintStart_toEndOf="@id/timer_reset"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/timer_play_pause"
android:layout_width="@dimen/timer_button_size"
android:layout_height="@dimen/timer_button_size"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginStart="@dimen/medium_margin"
android:layout_marginEnd="@dimen/small_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_toEndOf="@id/timer_reset"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/timer_drag_handle"
android:layout_width="@dimen/drag_handle_size"
android:layout_height="@dimen/drag_handle_size"
android:layout_marginStart="@dimen/medium_margin"
android:padding="@dimen/medium_margin"
android:src="@drawable/ic_drag_handle_vector"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/timer_play_pause"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -11,10 +11,12 @@
<dimen name="timer_button_small_size">50dp</dimen>
<dimen name="timer_button_size">56dp</dimen>
<dimen name="textview_drawable_size">24dp</dimen>
<dimen name="drag_handle_size">40dp</dimen>
<dimen name="clock_text_size">70sp</dimen>
<dimen name="clock_text_size_smaller">60sp</dimen>
<dimen name="alarm_text_size">60sp</dimen>
<dimen name="timer_text_size">@dimen/alarm_text_size</dimen>
<dimen name="stopwatch_text_size">80sp</dimen>
<dimen name="widget_time_text_size_small">48sp</dimen>
<dimen name="widget_details_text_size">14sp</dimen>

View File

@@ -18,6 +18,7 @@
<string name="swipe_right_to_dismiss">Swipe right to Dismiss, or left to Snooze.</string>
<string name="sort_by_creation_order">Creation order</string>
<string name="sort_by_alarm_time">Alarm time</string>
<string name="sort_by_timer_duration">Timer duration</string>
<string name="sort_by_day_and_alarm_time">Day and Alarm time</string>
<string name="analogue_clock">Analogue clock</string>
<string name="digital_clock">Digital clock</string>