feat: hide individual member calendars in the group view (Android)
The calendar filter, in group mode, lists the group's members (+ the shared group calendar) with toggles to hide each individually. Filtering is client-side via CalendarViewModel.hiddenGroupKeys (gm:<id> / gc), reset on group switch; members + colours loaded from the group detail. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,20 @@ fun CalendarFilterSheet(
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val state by vm.state.collectAsState()
|
||||
val groupMode = state.activeGroup != null
|
||||
|
||||
// In group mode the filter lists members (+ the group calendar) so they can
|
||||
// be hidden individually, Outlook-style; otherwise the normal calendars.
|
||||
val groupEntries: List<CalendarFilterEntry> = if (groupMode) {
|
||||
buildList {
|
||||
state.activeGroupMembers.forEach { m ->
|
||||
add(CalendarFilterEntry(groupMemberKey(m.id), m.displayName, m.color ?: "#4285f4"))
|
||||
}
|
||||
add(CalendarFilterEntry(GROUP_CALENDAR_KEY, tr("groups.calendar"), state.activeGroup?.groupCalendarColor ?: "#4285f4"))
|
||||
}
|
||||
} else emptyList()
|
||||
val rows = if (groupMode) groupEntries else events
|
||||
val hiddenSet = if (groupMode) state.hiddenGroupKeys else state.hiddenKeys
|
||||
|
||||
ModalBottomSheet(onDismissRequest = onDismiss) {
|
||||
Column(Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 8.dp)) {
|
||||
@@ -46,21 +60,24 @@ fun CalendarFilterSheet(
|
||||
) {
|
||||
Text(tr("filter.title"), style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.SemiBold)
|
||||
Row {
|
||||
TextButton(onClick = { vm.setHiddenCalendars(emptySet()) }) { Text(tr("filter.show_all")) }
|
||||
TextButton(onClick = {
|
||||
vm.setHiddenCalendars(events.map { it.key }.toSet())
|
||||
if (groupMode) vm.setHiddenGroupKeys(emptySet()) else vm.setHiddenCalendars(emptySet())
|
||||
}) { Text(tr("filter.show_all")) }
|
||||
TextButton(onClick = {
|
||||
if (groupMode) vm.setHiddenGroupKeys(rows.map { it.key }.toSet())
|
||||
else vm.setHiddenCalendars(rows.map { it.key }.toSet())
|
||||
}) { Text(tr("filter.hide_all")) }
|
||||
}
|
||||
}
|
||||
if (events.isEmpty()) {
|
||||
if (rows.isEmpty()) {
|
||||
Text(
|
||||
tr("filter.empty"),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(vertical = 24.dp),
|
||||
)
|
||||
}
|
||||
events.forEach { entry ->
|
||||
val visible = entry.key !in state.hiddenKeys
|
||||
rows.forEach { entry ->
|
||||
val visible = entry.key !in hiddenSet
|
||||
Row(
|
||||
Modifier.fillMaxWidth().padding(vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
@@ -73,7 +90,10 @@ fun CalendarFilterSheet(
|
||||
)
|
||||
Switch(
|
||||
checked = visible,
|
||||
onCheckedChange = { vm.setCalendarHidden(entry.key, hidden = !it) },
|
||||
onCheckedChange = {
|
||||
if (groupMode) vm.setGroupKeyHidden(entry.key, hidden = !it)
|
||||
else vm.setCalendarHidden(entry.key, hidden = !it)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.scarriffle.calendarr.data.SettingsStore
|
||||
import com.scarriffle.calendarr.domain.model.CalEvent
|
||||
import com.scarriffle.calendarr.domain.model.CalViewType
|
||||
import com.scarriffle.calendarr.domain.model.Group
|
||||
import com.scarriffle.calendarr.domain.model.GroupMember
|
||||
import com.scarriffle.calendarr.domain.model.WritableCalendar
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -42,8 +43,15 @@ data class CalendarUiState(
|
||||
// Group overlay: when non-null the calendar shows the group's combined view.
|
||||
val groups: List<Group> = emptyList(),
|
||||
val activeGroup: Group? = null,
|
||||
// Group overlay: full member list (for the filter) + per-member / group-cal
|
||||
// hidden keys ("gm:<userId>" / "gc"). In-memory; reset when switching group.
|
||||
val activeGroupMembers: List<GroupMember> = emptyList(),
|
||||
val hiddenGroupKeys: Set<String> = emptySet(),
|
||||
)
|
||||
|
||||
fun groupMemberKey(ownerId: Int): String = "gm:$ownerId"
|
||||
const val GROUP_CALENDAR_KEY = "gc"
|
||||
|
||||
@HiltViewModel
|
||||
class CalendarViewModel @Inject constructor(
|
||||
private val repository: CalendarRepository,
|
||||
@@ -252,9 +260,18 @@ class CalendarViewModel @Inject constructor(
|
||||
|
||||
private fun refreshFromCache() {
|
||||
val st = _state.value
|
||||
// In group mode the server already scopes + filters; show everything.
|
||||
// In group mode: server scopes/filters by privacy; locally honour the
|
||||
// per-member / group-calendar hide toggles (hiddenGroupKeys).
|
||||
val visible = if (st.activeGroup != null) {
|
||||
allCachedEvents
|
||||
val hg = st.hiddenGroupKeys
|
||||
if (hg.isEmpty()) allCachedEvents
|
||||
else allCachedEvents.filter { ev ->
|
||||
when {
|
||||
ev.isGroupEvent -> GROUP_CALENDAR_KEY !in hg
|
||||
ev.owner != null -> groupMemberKey(ev.owner.id ?: -1) !in hg
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val hidden = st.hiddenKeys
|
||||
val banished = st.banishedKeys
|
||||
@@ -329,9 +346,33 @@ class CalendarViewModel @Inject constructor(
|
||||
/** Flip between personal and a group's combined overlay; reloads the wide window. */
|
||||
fun switchGroup(group: Group?) {
|
||||
if (_state.value.activeGroup?.id == group?.id) return
|
||||
_state.update { it.copy(activeGroup = group) }
|
||||
_state.update {
|
||||
it.copy(activeGroup = group, hiddenGroupKeys = emptySet(), activeGroupMembers = emptyList())
|
||||
}
|
||||
invalidateCache()
|
||||
initialLoad()
|
||||
// Load the full member list (with server colours) for the filter sheet.
|
||||
if (group != null) {
|
||||
viewModelScope.launch {
|
||||
runCatching { repository.getGroup(group.id) }
|
||||
.onSuccess { g -> _state.update { it.copy(activeGroupMembers = g.members) } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Toggle a single member's calendar / the group calendar in the overlay. */
|
||||
fun setGroupKeyHidden(key: String, hidden: Boolean) {
|
||||
_state.update {
|
||||
val next = it.hiddenGroupKeys.toMutableSet().apply { if (hidden) add(key) else remove(key) }
|
||||
it.copy(hiddenGroupKeys = next)
|
||||
}
|
||||
refreshFromCache()
|
||||
}
|
||||
|
||||
/** Replace the group-overlay hidden set (bulk show/hide all). */
|
||||
fun setHiddenGroupKeys(keys: Set<String>) {
|
||||
_state.update { it.copy(hiddenGroupKeys = keys) }
|
||||
refreshFromCache()
|
||||
}
|
||||
|
||||
// ---- Writable calendars ----
|
||||
|
||||
Reference in New Issue
Block a user