diff --git a/app/src/main/kotlin/org/fossify/clock/activities/MainActivity.kt b/app/src/main/kotlin/org/fossify/clock/activities/MainActivity.kt index c3dd4d0f..10a6c12a 100644 --- a/app/src/main/kotlin/org/fossify/clock/activities/MainActivity.kt +++ b/app/src/main/kotlin/org/fossify/clock/activities/MainActivity.kt @@ -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) } } diff --git a/app/src/main/kotlin/org/fossify/clock/adapters/TimerAdapter.kt b/app/src/main/kotlin/org/fossify/clock/adapters/TimerAdapter.kt index 3c76a798..33113738 100644 --- a/app/src/main/kotlin/org/fossify/clock/adapters/TimerAdapter.kt +++ b/app/src/main/kotlin/org/fossify/clock/adapters/TimerAdapter.kt @@ -16,7 +16,12 @@ import org.fossify.clock.models.TimerEvent import org.fossify.clock.models.TimerState 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.getColoredDrawableWithColor +import org.fossify.commons.extensions.getFormattedDuration +import org.fossify.commons.extensions.openNotificationSettings import org.fossify.commons.views.MyRecyclerView import org.greenrobot.eventbus.EventBus @@ -41,6 +46,24 @@ class TimerAdapter( init { setupDragListener(true) + setHasStableIds(true) + } + + override fun getItemId(position: Int): Long { + return getItem(position).id!!.toLong() + } + + override fun submitList(list: MutableList?, commitCallback: Runnable?) { + val layoutManager = recyclerView.layoutManager!! + val recyclerViewState = layoutManager.onSaveInstanceState() + super.submitList(list) { + layoutManager.onRestoreInstanceState(recyclerViewState) + commitCallback?.run() + } + } + + override fun submitList(list: MutableList?) { + submitList(list, null) } override fun getActionMenuId() = R.menu.cab_alarms diff --git a/app/src/main/kotlin/org/fossify/clock/adapters/ViewPagerAdapter.kt b/app/src/main/kotlin/org/fossify/clock/adapters/ViewPagerAdapter.kt index d5cb5fba..80547d80 100644 --- a/app/src/main/kotlin/org/fossify/clock/adapters/ViewPagerAdapter.kt +++ b/app/src/main/kotlin/org/fossify/clock/adapters/ViewPagerAdapter.kt @@ -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() } diff --git a/app/src/main/kotlin/org/fossify/clock/dialogs/ChangeTimerSortDialog.kt b/app/src/main/kotlin/org/fossify/clock/dialogs/ChangeTimerSortDialog.kt new file mode 100644 index 00000000..59d67112 --- /dev/null +++ b/app/src/main/kotlin/org/fossify/clock/dialogs/ChangeTimerSortDialog.kt @@ -0,0 +1,39 @@ +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 + +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 + 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 + else -> SORT_BY_CREATION_ORDER + } + + activity.config.timerSort = sort + callback() + } +} diff --git a/app/src/main/kotlin/org/fossify/clock/fragments/TimerFragment.kt b/app/src/main/kotlin/org/fossify/clock/fragments/TimerFragment.kt index 7c449b7b..8f719746 100644 --- a/app/src/main/kotlin/org/fossify/clock/fragments/TimerFragment.kt +++ b/app/src/main/kotlin/org/fossify/clock/fragments/TimerFragment.kt @@ -10,24 +10,26 @@ 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_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 +45,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 +79,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 +96,47 @@ 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) -> 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 } + 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 +154,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 +173,5 @@ class TimerFragment : Fragment() { } } } + +private const val INVALID_POSITION = -1 diff --git a/app/src/main/kotlin/org/fossify/clock/helpers/Config.kt b/app/src/main/kotlin/org/fossify/clock/helpers/Config.kt index 873006a7..08cc029f 100644 --- a/app/src/main/kotlin/org/fossify/clock/helpers/Config.kt +++ b/app/src/main/kotlin/org/fossify/clock/helpers/Config.kt @@ -59,6 +59,10 @@ 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 timerSort: Int + get() = prefs.getInt(TIMERS_SORT_BY, SORT_BY_CREATION_ORDER) + set(timerSort) = prefs.edit().putInt(TIMERS_SORT_BY, timerSort).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() diff --git a/app/src/main/kotlin/org/fossify/clock/helpers/Constants.kt b/app/src/main/kotlin/org/fossify/clock/helpers/Constants.kt index 53772ba1..cb9d3015 100644 --- a/app/src/main/kotlin/org/fossify/clock/helpers/Constants.kt +++ b/app/src/main/kotlin/org/fossify/clock/helpers/Constants.kt @@ -24,6 +24,7 @@ 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 TIMERS_SORT_BY = "timers_sort_by" 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 +71,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 diff --git a/app/src/main/res/layout/dialog_change_timer_sort.xml b/app/src/main/res/layout/dialog_change_timer_sort.xml new file mode 100644 index 00000000..1b6b3ac0 --- /dev/null +++ b/app/src/main/res/layout/dialog_change_timer_sort.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5df0ed1b..5990fe5e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -18,6 +18,7 @@ Swipe right to Dismiss, or left to Snooze. Creation order Alarm time + Timer duration Day and Alarm time Analogue clock Digital clock