Skip to content

Commit 0a3a4af

Browse files
authored
Merge pull request #68 from babogoos/Add-session-language-filter
Add session language filter.
2 parents 3496cbf + ae1a89d commit 0a3a4af

File tree

4 files changed

+97
-9
lines changed

4 files changed

+97
-9
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package app.opass.ccip.model
2+
3+
import android.content.Context
4+
import app.opass.ccip.util.LocaleUtil
5+
import java.util.*
6+
7+
data class SessionLang(
8+
val id: String,
9+
val zh: Zh,
10+
val en: En
11+
) {
12+
fun getDetails(context: Context) =
13+
if (LocaleUtil.getCurrentLocale(context).language == Locale("zh").language) zh else en
14+
15+
interface LocalizedDetail {
16+
val name: String
17+
}
18+
19+
data class Zh(override val name: String) : LocalizedDetail
20+
data class En(override val name: String) : LocalizedDetail
21+
}

app/src/main/java/app/opass/ccip/ui/schedule/ScheduleFilterAdapter.kt

+13
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder
1313
import app.opass.ccip.R
1414
import app.opass.ccip.extension.dpToPx
1515
import app.opass.ccip.extension.updateMargin
16+
import app.opass.ccip.model.SessionLang
1617
import app.opass.ccip.model.SessionTag
1718
import app.opass.ccip.model.SessionType
1819
import com.google.android.material.chip.Chip
@@ -52,6 +53,7 @@ class ScheduleFilterAdapter(
5253
is SessionFilter.StarredFilter -> context.getString(R.string.bookmarked)
5354
is SessionFilter.TagFilter -> item.tag.getDetails(context).name
5455
is SessionFilter.TypeFilter -> item.type.getDetails(context).name
56+
is SessionFilter.LangFilter -> item.lang.getDetails(context).name
5557
}
5658
isChipIconVisible = item is SessionFilter.StarredFilter
5759
checkedIcon =
@@ -99,6 +101,13 @@ class ScheduleFilterAdapter(
99101
merged.add(firstTagFilterIdx, context.resources.getText(R.string.tag))
100102
}
101103
}
104+
merged
105+
.indexOfFirst { it is SessionFilter.LangFilter }
106+
.let { firstLangFilterIdx ->
107+
if (firstLangFilterIdx != -1) {
108+
merged.add(firstLangFilterIdx, context.resources.getText(R.string.lang))
109+
}
110+
}
102111
super.submitList(merged)
103112
}
104113
}
@@ -138,6 +147,7 @@ class FilterHeaderChipAdapter(
138147
is SessionFilter.StarredFilter -> context.getString(R.string.bookmarked)
139148
is SessionFilter.TagFilter -> item.tag.getDetails(context).name
140149
is SessionFilter.TypeFilter -> item.type.getDetails(context).name
150+
is SessionFilter.LangFilter -> item.lang.getDetails(context).name
141151
}
142152
isChipIconVisible = item is SessionFilter.StarredFilter
143153
chipIcon = AppCompatResources.getDrawable(context, R.drawable.ic_bookmark_black_24dp)
@@ -152,6 +162,7 @@ sealed class SessionFilter(val isActivated: Boolean) {
152162
class StarredFilter(isActivated: Boolean) : SessionFilter(isActivated)
153163
class TagFilter(val tag: SessionTag, isActivated: Boolean) : SessionFilter(isActivated)
154164
class TypeFilter(val type: SessionType, isActivated: Boolean) : SessionFilter(isActivated)
165+
class LangFilter(val lang: SessionLang, isActivated: Boolean) : SessionFilter(isActivated)
155166
}
156167

157168
private object Differ : DiffUtil.ItemCallback<Any>() {
@@ -163,6 +174,8 @@ private object Differ : DiffUtil.ItemCallback<Any>() {
163174
-> oldItem.tag.id == newItem.tag.id
164175
(oldItem is SessionFilter.TypeFilter && newItem is SessionFilter.TypeFilter)
165176
-> oldItem.type.id == newItem.type.id
177+
(oldItem is SessionFilter.LangFilter && newItem is SessionFilter.LangFilter)
178+
-> oldItem.lang.id == newItem.lang.id
166179
else -> oldItem === newItem
167180
}
168181
}

app/src/main/java/app/opass/ccip/ui/schedule/ScheduleFilterFragment.kt

+11-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ class ScheduleFilterFragment : Fragment() {
6969
spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
7070
override fun getSpanSize(position: Int): Int {
7171
return when ((adapter as ScheduleFilterAdapter).currentList[position]) {
72-
is SessionFilter.TagFilter, is SessionFilter.TypeFilter -> 1
72+
is SessionFilter.TagFilter,
73+
is SessionFilter.TypeFilter,
74+
is SessionFilter.LangFilter -> 1
7375
else -> 2
7476
}
7577
}
@@ -80,6 +82,7 @@ class ScheduleFilterFragment : Fragment() {
8082
is SessionFilter.StarredFilter -> vm.toggleStarFilter()
8183
is SessionFilter.TagFilter -> vm.toggleFilterTag(filter.tag.id)
8284
is SessionFilter.TypeFilter -> vm.toggleFilterType(filter.type.id)
85+
is SessionFilter.LangFilter -> vm.toggleLangType(filter.lang.id)
8386
}
8487
}
8588
}
@@ -115,22 +118,29 @@ class ScheduleFilterFragment : Fragment() {
115118
val starredOnly = vm.showStarredOnly.value!!
116119
val types = vm.types.value ?: return@update
117120
val tags = vm.tags.value ?: return@update
121+
val langList = vm.langList.value
118122
val selectedTypes = vm.selectedTypeIds.value!!
119123
val selectedTags = vm.selectedTagIds.value!!
124+
val selectedLangList = vm.selectedLangIds.value!!
120125
val filters = mutableListOf<SessionFilter>(SessionFilter.StarredFilter(starredOnly))
121126
types.map { type ->
122127
SessionFilter.TypeFilter(type, selectedTypes.contains(type.id))
123128
}.let(filters::addAll)
124129
tags.map { tag ->
125130
SessionFilter.TagFilter(tag, selectedTags.contains(tag.id))
126131
}.let(filters::addAll)
132+
langList?.map { sessionLang ->
133+
SessionFilter.LangFilter(sessionLang, selectedLangList.contains(sessionLang.id))
134+
}?.let(filters::addAll)
127135
value = filters
128136
}
129137
addSource(vm.showStarredOnly) { update() }
130138
addSource(vm.types) { update() }
131139
addSource(vm.tags) { update() }
140+
addSource(vm.langList) { update() }
132141
addSource(vm.selectedTypeIds) { update() }
133142
addSource(vm.selectedTagIds) { update() }
143+
addSource(vm.selectedLangIds) { update() }
134144
}.observe(viewLifecycleOwner) { filters ->
135145
(binding.filterHeaderRv.adapter as FilterHeaderChipAdapter).submitList(filters.filter { f -> f.isActivated })
136146
(binding.filterContentRv.adapter as ScheduleFilterAdapter).submitFilters(filters)

app/src/main/java/app/opass/ccip/ui/schedule/ScheduleViewModel.kt

+52-8
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@ package app.opass.ccip.ui.schedule
33
import android.app.Application
44
import androidx.lifecycle.*
55
import app.opass.ccip.extension.debounce
6-
import app.opass.ccip.model.ConfSchedule
7-
import app.opass.ccip.model.Session
8-
import app.opass.ccip.model.SessionTag
9-
import app.opass.ccip.model.SessionType
6+
import app.opass.ccip.model.*
107
import app.opass.ccip.util.PreferenceUtil
118
import com.google.gson.internal.bind.util.ISO8601Utils
129
import kotlinx.coroutines.Dispatchers
@@ -35,6 +32,7 @@ fun List<Session>.groupedByDate(): Map<String, List<Session>> =
3532
private const val KEY_SHOW_STARRED_ONLY = "showStarredOnly"
3633
private const val KEY_SELECTED_TAG_IDS = "selectedTagIds"
3734
private const val KEY_SELECTED_TYPE_IDS = "selectedTypeIds"
35+
private const val KEY_SELECTED_LANG_IDS = "selectedLangIds"
3836
private const val KEY_SHOW_SEARCH_PANEL = "showSearchPanel"
3937

4038
class ScheduleViewModel(application: Application, stateHandle: SavedStateHandle) : AndroidViewModel(application) {
@@ -46,24 +44,44 @@ class ScheduleViewModel(application: Application, stateHandle: SavedStateHandle)
4644
}
4745
val tags: LiveData<List<SessionTag>?> = schedule.map { schedule -> schedule?.tags }
4846
val types: LiveData<List<SessionType>?> = schedule.map { schedule -> schedule?.sessionTypes }
47+
val langList: LiveData<List<SessionLang>?> = schedule.map { schedule ->
48+
val langList = schedule?.sessions?.mapNotNull { it.language }?.distinct()
49+
if (langList.isNullOrEmpty()) {
50+
null
51+
} else {
52+
langList.map { lang ->
53+
SessionLang(lang, SessionLang.Zh(lang), SessionLang.En(lang))
54+
}
55+
}
56+
}
4957

5058
val showStarredOnly = stateHandle.getLiveData(KEY_SHOW_STARRED_ONLY, false)
5159
val selectedTagIds = stateHandle.getLiveData(KEY_SELECTED_TAG_IDS, emptyList<String>())
5260
val selectedTypeIds = stateHandle.getLiveData(KEY_SELECTED_TYPE_IDS, emptyList<String>())
61+
val selectedLangIds = stateHandle.getLiveData(KEY_SELECTED_LANG_IDS, emptyList<String>())
5362
val shouldShowSearchPanel = stateHandle.getLiveData(KEY_SHOW_SEARCH_PANEL, false)
5463

5564
private val searchTerm: MutableLiveData<String> = MutableLiveData("")
5665
private val filterConfig: LiveData<FilterConfig> = MediatorLiveData<FilterConfig>().apply {
5766
val update = {
58-
value = FilterConfig(sessionsGroupedByDate.value, showStarredOnly.value!!, selectedTagIds.value!!, selectedTypeIds.value!!, searchTerm.value!!)
67+
value = FilterConfig(
68+
sessionsGroupedByDate.value,
69+
showStarredOnly.value!!,
70+
selectedTagIds.value!!,
71+
selectedTypeIds.value!!,
72+
selectedLangIds.value!!,
73+
searchTerm.value!!
74+
)
5975
}
6076
addSource(sessionsGroupedByDate) { update() }
6177
addSource(showStarredOnly) { update() }
6278
addSource(selectedTagIds) { update() }
6379
addSource(selectedTypeIds) { update() }
80+
addSource(selectedLangIds) { update() }
6481
addSource(searchTerm) { update() }
6582
}.debounce(100)
66-
val groupedSessionsToShow: LiveData<Map<String, List<Session>>?> = filterConfig.switchMap { (sessions, starredOnly, selectedTagIds, selectedTypeIds, searchText) ->
83+
val groupedSessionsToShow: LiveData<Map<String, List<Session>>?> = filterConfig.switchMap {
84+
(sessions, starredOnly, selectedTagIds, selectedTypeIds, selectedLangIds, searchText) ->
6785
liveData(viewModelScope.coroutineContext + Dispatchers.Default) {
6886
if (sessions == null) {
6987
emit(null)
@@ -87,6 +105,14 @@ class ScheduleViewModel(application: Application, stateHandle: SavedStateHandle)
87105
}
88106
}
89107

108+
if (selectedLangIds.isNotEmpty()) {
109+
result = result.mapValues { (_, sessions) ->
110+
sessions.filter { session ->
111+
selectedLangIds.any { id -> session.language == id }
112+
}
113+
}
114+
}
115+
90116
if (hasSearchTerm()) {
91117
val searchResult = result.mapValues { (_, sessions) ->
92118
sessions.filter { session ->
@@ -108,11 +134,13 @@ class ScheduleViewModel(application: Application, stateHandle: SavedStateHandle)
108134
val starredOnly = showStarredOnly.value ?: false
109135
val hasSelectedTags = selectedTagIds.value?.isNotEmpty() ?: false
110136
val hasSelectedTypes = selectedTypeIds.value?.isNotEmpty() ?: false
111-
value = starredOnly || hasSelectedTags || hasSelectedTypes
137+
val hasSelectedLangList = selectedLangIds.value?.isNotEmpty() ?: false
138+
value = starredOnly || hasSelectedTags || hasSelectedTypes || hasSelectedLangList
112139
}
113140
addSource(showStarredOnly) { update() }
114141
addSource(selectedTagIds) { update() }
115142
addSource(selectedTypeIds) { update() }
143+
addSource(selectedLangIds) { update() }
116144
}
117145
val shouldFilterSheetCollapse = MediatorLiveData<Boolean>().apply {
118146
val update = {
@@ -129,12 +157,14 @@ class ScheduleViewModel(application: Application, stateHandle: SavedStateHandle)
129157
val starredOnly = showStarredOnly.value ?: false
130158
val hasSelectedTags = selectedTagIds.value?.isNotEmpty() ?: false
131159
val hasSelectedTypes = selectedTypeIds.value?.isNotEmpty() ?: false
132-
value = scheduleReady && !starredOnly && !hasSelectedTags && !hasSelectedTypes
160+
val hasSelectedLangList = selectedLangIds.value?.isNotEmpty() ?: false
161+
value = scheduleReady && !starredOnly && !hasSelectedTags && !hasSelectedTypes && !hasSelectedLangList
133162
}
134163
addSource(isScheduleReady) { update() }
135164
addSource(showStarredOnly) { update() }
136165
addSource(selectedTagIds) { update() }
137166
addSource(selectedTypeIds) { update() }
167+
addSource(selectedLangIds) { update() }
138168
}
139169

140170
init {
@@ -160,13 +190,18 @@ class ScheduleViewModel(application: Application, stateHandle: SavedStateHandle)
160190
types.value?.map(SessionType::id)?.let { validTypeIds ->
161191
selectedTypeIds.value = selectedTypeIds.value!!.filter { id -> validTypeIds.contains(id) }
162192
}
193+
194+
langList.value?.map(SessionLang::id)?.let { validLangIds ->
195+
selectedLangIds.value = selectedLangIds.value!!.filter { id -> validLangIds.contains(id) }
196+
}
163197
}
164198
}
165199

166200
fun clearFilter() {
167201
showStarredOnly.value = false
168202
selectedTagIds.value = emptyList()
169203
selectedTypeIds.value = emptyList()
204+
selectedLangIds.value = emptyList()
170205
}
171206

172207
fun toggleFilterTag(idToToggle: String) {
@@ -185,6 +220,14 @@ class ScheduleViewModel(application: Application, stateHandle: SavedStateHandle)
185220
}
186221
}
187222

223+
fun toggleLangType(idToToggle: String) {
224+
if (selectedLangIds.value!!.contains(idToToggle)) {
225+
selectedLangIds.value = selectedLangIds.value!!.filterNot { id -> id == idToToggle }
226+
} else {
227+
selectedLangIds.value = selectedLangIds.value!! + idToToggle
228+
}
229+
}
230+
188231
fun toggleStarFilter() {
189232
showStarredOnly.value = showStarredOnly.value!!.not()
190233
}
@@ -210,5 +253,6 @@ private data class FilterConfig(
210253
val showStarredOnly: Boolean,
211254
val selectedTagIds: List<String>,
212255
val selectedTypeIds: List<String>,
256+
val selectedLangList: List<String>,
213257
val searchTerm: String
214258
)

0 commit comments

Comments
 (0)