Skip to content

Commit 9a1f13d

Browse files
committed
add swipe to filter to stat detail data distribution
1 parent 020e092 commit 9a1f13d

File tree

7 files changed

+174
-11
lines changed

7 files changed

+174
-11
lines changed

core/src/main/java/com/example/util/simpletimetracker/core/extension/RecyclerExtensions.kt

Lines changed: 102 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
package com.example.util.simpletimetracker.core.extension
22

3+
import android.graphics.Canvas
4+
import android.graphics.Paint
5+
import android.graphics.PorterDuff
6+
import android.graphics.PorterDuffXfermode
7+
import android.graphics.drawable.ColorDrawable
8+
import androidx.annotation.ColorInt
9+
import androidx.annotation.DrawableRes
10+
import androidx.core.content.ContextCompat
311
import androidx.recyclerview.widget.ItemTouchHelper
412
import androidx.recyclerview.widget.RecyclerView
513
import com.example.util.simpletimetracker.feature_base_adapter.BaseRecyclerAdapter
614
import com.example.util.simpletimetracker.feature_base_adapter.ViewHolderType
715
import java.util.Collections
16+
import com.example.util.simpletimetracker.domain.extension.orZero
17+
import com.example.util.simpletimetracker.feature_views.extension.dpToPx
818

919
fun RecyclerView.onItemMoved(
1020
getIsSelectable: (RecyclerView.ViewHolder?) -> Boolean = { true },
@@ -42,11 +52,7 @@ fun RecyclerView.onItemMoved(
4252
recyclerView: RecyclerView,
4353
viewHolder: RecyclerView.ViewHolder,
4454
): Int {
45-
return if (getIsSelectable(viewHolder)) {
46-
dragDirections
47-
} else {
48-
0
49-
}
55+
return if (getIsSelectable(viewHolder)) dragDirections else 0
5056
}
5157

5258
override fun onMove(
@@ -86,5 +92,96 @@ fun RecyclerView.onItemMoved(
8692
}
8793
}
8894

95+
(ItemTouchHelper(helper)).attachToRecyclerView(this)
96+
}
97+
98+
fun RecyclerView.onItemSwiped(
99+
@DrawableRes iconRes: Int,
100+
@ColorInt iconColor: Int,
101+
@ColorInt backgroundColor: Int,
102+
getIsSelectable: (RecyclerView.ViewHolder?) -> Boolean = { true },
103+
onSwiped: (RecyclerView.ViewHolder?) -> Unit,
104+
) {
105+
106+
val swipeDirections = ItemTouchHelper.START
107+
val deleteIcon by lazy {
108+
ContextCompat.getDrawable(context, iconRes)?.mutate()?.apply { setTint(iconColor) }
109+
}
110+
val intrinsicWidth = deleteIcon?.intrinsicWidth.orZero()
111+
val intrinsicHeight = deleteIcon?.intrinsicHeight.orZero()
112+
val background = ColorDrawable()
113+
val clearPaint = Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) }
114+
115+
val helper = object : ItemTouchHelper.SimpleCallback(0, 0) {
116+
117+
override fun getSwipeDirs(
118+
recyclerView: RecyclerView,
119+
viewHolder: RecyclerView.ViewHolder,
120+
): Int {
121+
return if (getIsSelectable(viewHolder)) swipeDirections else 0
122+
}
123+
124+
override fun onMove(
125+
recyclerView: RecyclerView,
126+
viewHolder: RecyclerView.ViewHolder,
127+
target: RecyclerView.ViewHolder,
128+
): Boolean {
129+
return false
130+
}
131+
132+
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
133+
onSwiped(viewHolder)
134+
}
135+
136+
override fun onChildDraw(
137+
canvas: Canvas,
138+
recyclerView: RecyclerView,
139+
viewHolder: RecyclerView.ViewHolder,
140+
dX: Float,
141+
dY: Float,
142+
actionState: Int,
143+
isCurrentlyActive: Boolean,
144+
) {
145+
val itemView = viewHolder.itemView
146+
val isCanceled = dX == 0f
147+
148+
if (isCanceled) {
149+
canvas.drawRect(
150+
itemView.right + dX,
151+
itemView.top.toFloat(),
152+
itemView.right.toFloat(),
153+
itemView.bottom.toFloat(),
154+
clearPaint,
155+
)
156+
super.onChildDraw(canvas, recyclerView, viewHolder, dX, dY, actionState, false)
157+
return
158+
}
159+
160+
// Draw the red delete background
161+
background.color = backgroundColor
162+
background.setBounds(
163+
itemView.right + dX.toInt(),
164+
itemView.top,
165+
itemView.right,
166+
itemView.bottom,
167+
)
168+
background.draw(canvas)
169+
170+
// Calculate position of delete icon
171+
val itemHeight = itemView.bottom - itemView.top
172+
val iconMargin = 16.dpToPx()
173+
val iconTop = itemView.top + (itemHeight - intrinsicHeight) / 2
174+
val iconLeft = itemView.right - iconMargin - intrinsicWidth
175+
val iconRight = itemView.right - iconMargin
176+
val iconBottom = iconTop + intrinsicHeight
177+
178+
// Draw the delete icon
179+
deleteIcon?.setBounds(iconLeft, iconTop, iconRight, iconBottom)
180+
deleteIcon?.draw(canvas)
181+
182+
super.onChildDraw(canvas, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
183+
}
184+
}
185+
89186
(ItemTouchHelper(helper)).attachToRecyclerView(this)
90187
}

features/feature_statistics_detail/src/main/java/com/example/util/simpletimetracker/feature_statistics_detail/view/StatisticsDetailFragment.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,21 @@ import com.example.util.simpletimetracker.core.dialog.DateTimeDialogListener
1010
import com.example.util.simpletimetracker.core.dialog.DurationDialogListener
1111
import com.example.util.simpletimetracker.core.dialog.OptionsListDialogListener
1212
import com.example.util.simpletimetracker.core.dialog.RecordsFilterListener
13+
import com.example.util.simpletimetracker.core.extension.onItemSwiped
1314
import com.example.util.simpletimetracker.core.extension.setSharedTransitions
1415
import com.example.util.simpletimetracker.core.extension.toViewData
1516
import com.example.util.simpletimetracker.core.utils.InsetConfiguration
1617
import com.example.util.simpletimetracker.core.utils.fragmentArgumentDelegate
1718
import com.example.util.simpletimetracker.core.viewData.RangesViewData
19+
import com.example.util.simpletimetracker.domain.extension.orFalse
1820
import com.example.util.simpletimetracker.domain.record.model.Range
1921
import com.example.util.simpletimetracker.feature_base_adapter.BaseRecyclerAdapter
22+
import com.example.util.simpletimetracker.feature_base_adapter.ViewHolderType
2023
import com.example.util.simpletimetracker.feature_base_adapter.hint.createHintAdapterDelegate
24+
import com.example.util.simpletimetracker.feature_base_adapter.statistics.StatisticsSelectableViewData
2125
import com.example.util.simpletimetracker.feature_base_adapter.statistics.createStatisticsAdapterDelegate
2226
import com.example.util.simpletimetracker.feature_base_adapter.statistics.createStatisticsSelectableAdapterDelegate
27+
import com.example.util.simpletimetracker.feature_statistics_detail.R
2328
import com.example.util.simpletimetracker.feature_statistics_detail.adapter.createStatisticsDetailBarChartAdapterDelegate
2429
import com.example.util.simpletimetracker.feature_statistics_detail.adapter.createStatisticsDetailButtonAdapterDelegate
2530
import com.example.util.simpletimetracker.feature_statistics_detail.adapter.createStatisticsDetailButtonsRowAdapterDelegate
@@ -35,6 +40,8 @@ import com.example.util.simpletimetracker.feature_statistics_detail.adapter.crea
3540
import com.example.util.simpletimetracker.feature_statistics_detail.viewData.StatisticsDetailPreviewCompositeViewData
3641
import com.example.util.simpletimetracker.feature_statistics_detail.viewData.StatisticsDetailPreviewViewData
3742
import com.example.util.simpletimetracker.feature_statistics_detail.viewModel.StatisticsDetailViewModel
43+
import com.example.util.simpletimetracker.feature_views.ColorUtils
44+
import com.example.util.simpletimetracker.feature_views.extension.getThemedAttr
3845
import com.example.util.simpletimetracker.feature_views.extension.setOnClick
3946
import com.example.util.simpletimetracker.feature_views.extension.setOnLongClick
4047
import com.example.util.simpletimetracker.feature_views.extension.visible
@@ -125,6 +132,7 @@ class StatisticsDetailFragment :
125132
btnStatisticsDetailNext.setOnClick(viewModel::onNextClick)
126133
btnStatisticsDetailToday.setOnClick { spinnerStatisticsDetail.performClick() }
127134
btnStatisticsDetailToday.setOnLongClick(viewModel::onTodayClick)
135+
initOnItemSwiped()
128136
}
129137

130138
override fun onDateTimeSet(timestamp: Long, tag: String?) {
@@ -211,6 +219,30 @@ class StatisticsDetailFragment :
211219
binding.rvStatisticsDetailContent.apply { post { smoothScrollToPosition(0) } }
212220
}
213221

222+
private fun initOnItemSwiped() = with(binding) {
223+
fun ViewHolderType.canBeSwiped(): Boolean {
224+
return this is StatisticsSelectableViewData
225+
}
226+
227+
val context = rvStatisticsDetailContent.context
228+
rvStatisticsDetailContent.onItemSwiped(
229+
iconRes = R.drawable.delete, // TODO
230+
iconColor = context.getThemedAttr(R.attr.appContrastColor),
231+
backgroundColor = ColorUtils.changeAlpha(
232+
context.getThemedAttr(R.attr.appContrastColor), 0.10f,
233+
),
234+
getIsSelectable = { viewHolder ->
235+
viewHolder?.adapterPosition
236+
?.let { contentAdapter.getItemByPosition(it) }
237+
?.canBeSwiped()
238+
.orFalse()
239+
},
240+
onSwiped = { viewHolder ->
241+
viewModel.onSwiped(viewHolder as? ViewHolderType)
242+
},
243+
)
244+
}
245+
214246
companion object {
215247
private const val ARGS_PARAMS = "args_params"
216248

features/feature_statistics_detail/src/main/java/com/example/util/simpletimetracker/feature_statistics_detail/viewModel/StatisticsDetailViewModel.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import com.example.util.simpletimetracker.feature_statistics_detail.adapter.Stat
2222
import com.example.util.simpletimetracker.feature_statistics_detail.customView.SeriesCalendarView
2323
import com.example.util.simpletimetracker.feature_statistics_detail.interactor.StatisticsDetailContentInteractor
2424
import com.example.util.simpletimetracker.feature_statistics_detail.mapper.StatisticsDetailOptionsListMapper
25+
import com.example.util.simpletimetracker.feature_statistics_detail.model.DataDistributionMode
2526
import com.example.util.simpletimetracker.feature_statistics_detail.model.StatisticsDetailOptionsListItem
2627
import com.example.util.simpletimetracker.feature_statistics_detail.viewData.StatisticsDetailCardInternalViewData
2728
import com.example.util.simpletimetracker.feature_statistics_detail.viewData.StatisticsDetailClickableLongest
@@ -208,6 +209,11 @@ class StatisticsDetailViewModel @Inject constructor(
208209
}
209210
}
210211

212+
fun onSwiped(item: ViewHolderType?) {
213+
item ?: return
214+
dataDistributionDelegate.onStatisticsItemSwiped(item)
215+
}
216+
211217
fun onPreviousClick() {
212218
rangeDelegate.onPreviousClick()
213219
}
@@ -360,6 +366,10 @@ class StatisticsDetailViewModel @Inject constructor(
360366
streaksDelegate.updateStreaksGoalViewData()
361367
updateViewData()
362368
}
369+
370+
override fun onStatisticsHidden(id: Long, mode: DataDistributionMode) {
371+
filterDelegate.onStatisticsHidden(id, mode)
372+
}
363373
}
364374
}
365375
}

features/feature_statistics_detail/src/main/java/com/example/util/simpletimetracker/feature_statistics_detail/viewModel/delegate/StatisticsDetailDataDistributionViewModelDelegate.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import androidx.lifecycle.MutableLiveData
55
import com.example.util.simpletimetracker.core.base.ViewModelDelegate
66
import com.example.util.simpletimetracker.core.extension.set
77
import com.example.util.simpletimetracker.core.view.buttonsRowView.ButtonsRowViewData
8+
import com.example.util.simpletimetracker.feature_base_adapter.ViewHolderType
9+
import com.example.util.simpletimetracker.feature_base_adapter.statistics.StatisticsSelectableViewData
810
import com.example.util.simpletimetracker.feature_base_adapter.statistics.StatisticsViewData
911
import com.example.util.simpletimetracker.feature_statistics_detail.interactor.StatisticsDetailDataDistributionInteractor
1012
import com.example.util.simpletimetracker.feature_statistics_detail.model.DataDistributionMode
@@ -57,6 +59,14 @@ class StatisticsDetailDataDistributionViewModelDelegate @Inject constructor(
5759
updateViewData(animate = false)
5860
}
5961

62+
fun onStatisticsItemSwiped(item: ViewHolderType) {
63+
val id = (item as? StatisticsSelectableViewData)?.data?.id ?: return
64+
parent?.onStatisticsHidden(
65+
id = id,
66+
mode = dataDistributionMode,
67+
)
68+
}
69+
6070
fun updateViewData(
6171
animate: Boolean = true,
6272
) {

features/feature_statistics_detail/src/main/java/com/example/util/simpletimetracker/feature_statistics_detail/viewModel/delegate/StatisticsDetailFilterViewModelDelegate.kt

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.example.util.simpletimetracker.domain.prefs.interactor.PrefsInteracto
99
import com.example.util.simpletimetracker.domain.record.model.RecordBase
1010
import com.example.util.simpletimetracker.domain.record.model.RecordsFilter
1111
import com.example.util.simpletimetracker.feature_statistics_detail.R
12+
import com.example.util.simpletimetracker.feature_statistics_detail.model.DataDistributionMode
1213
import com.example.util.simpletimetracker.navigation.Router
1314
import com.example.util.simpletimetracker.navigation.params.screen.RecordsFilterParam
1415
import com.example.util.simpletimetracker.navigation.params.screen.RecordsFilterParams
@@ -83,12 +84,15 @@ class StatisticsDetailFilterViewModelDelegate @Inject constructor(
8384

8485
fun onTypesFilterDismissed(tag: String) {
8586
if (tag !in listOf(FILTER_TAG, COMPARE_TAG)) return
87+
onFiltersChanged()
88+
}
8689

87-
loadJob?.cancel()
88-
loadJob = delegateScope.launch {
89-
loadRecordsCache()
90-
parent?.onTypesFilterDismissed()
91-
}
90+
fun onStatisticsHidden(id: Long, mode: DataDistributionMode) {
91+
// TODO change filter
92+
// TODO check tag consistency
93+
// TODO multitask?
94+
// TODO untracked?
95+
onFiltersChanged()
9296
}
9397

9498
fun provideRecords(): List<RecordBase> {
@@ -107,6 +111,14 @@ class StatisticsDetailFilterViewModelDelegate @Inject constructor(
107111
return comparisonFilter.filter { it !is RecordsFilter.Date }
108112
}
109113

114+
private fun onFiltersChanged() {
115+
loadJob?.cancel()
116+
loadJob = delegateScope.launch {
117+
loadRecordsCache()
118+
parent?.onTypesFilterDismissed()
119+
}
120+
}
121+
110122
private suspend fun openFilter(
111123
tag: String,
112124
title: String,

features/feature_statistics_detail/src/main/java/com/example/util/simpletimetracker/feature_statistics_detail/viewModel/delegate/StatisticsDetailViewModelDelegate.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.example.util.simpletimetracker.feature_statistics_detail.viewModel.d
33
import com.example.util.simpletimetracker.domain.statistics.model.RangeLength
44
import com.example.util.simpletimetracker.domain.record.model.RecordBase
55
import com.example.util.simpletimetracker.domain.record.model.RecordsFilter
6+
import com.example.util.simpletimetracker.feature_statistics_detail.model.DataDistributionMode
67
import com.example.util.simpletimetracker.navigation.params.screen.StatisticsDetailParams
78

89
interface StatisticsDetailViewModelDelegate {
@@ -23,5 +24,6 @@ interface StatisticsDetailViewModelDelegate {
2324
fun updateViewData()
2425
fun getDateFilter(): List<RecordsFilter>
2526
suspend fun onTypesFilterDismissed()
27+
fun onStatisticsHidden(id: Long, mode: DataDistributionMode)
2628
}
2729
}

features/feature_statistics_detail/src/main/res/layout/statistics_detail_preview_more_item.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
xmlns:app="http://schemas.android.com/apk/res-auto"
44
android:layout_width="wrap_content"
55
android:layout_height="wrap_content"
6-
app:cardBackgroundColor="?appBackgroundColor"
6+
app:cardBackgroundColor="?appCardBackgroundColor"
77
app:cardCornerRadius="8dp"
88
app:cardElevation="4dp"
99
app:cardPreventCornerOverlap="false"

0 commit comments

Comments
 (0)