Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for hiding subjects from the timetable #455

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import com.sapuseven.untis.ui.functional.bottomInsets
import com.sapuseven.untis.ui.functional.insetsPaddingValues
import com.sapuseven.untis.ui.models.NavItemShortcut
import com.sapuseven.untis.ui.preferences.convertRangeToPair
import com.sapuseven.untis.ui.preferences.decodeStoredTimetableValue
import com.sapuseven.untis.ui.weekview.*
import io.sentry.Breadcrumb
import io.sentry.Sentry
Expand Down Expand Up @@ -838,6 +839,7 @@ class NewMainAppState @OptIn(ExperimentalMaterial3Api::class) constructor(
private suspend fun prepareItems(
items: List<TimegridItem>
): List<TimegridItem> {
val hiddenSubjects = decodeStoredTimetableValue(preferences.timetableHiddenElements.getValue()).orEmpty()
val newItems = mergeItems(items.mapNotNull { item ->
if (item.periodData.isCancelled() && preferences.timetableHideCancelled.getValue())
return@mapNotNull null
Expand All @@ -853,6 +855,9 @@ class NewMainAppState @OptIn(ExperimentalMaterial3Api::class) constructor(
&& item.periodData.element.backColor != UNTIS_DEFAULT_COLOR
}
}
if (item.periodData.subjects.isNotEmpty() && hiddenSubjects.containsAll(item.periodData.subjects)) {
return@mapNotNull null
}
item
})
colorItems(newItems)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import com.sapuseven.untis.BuildConfig
import com.sapuseven.untis.R
import com.sapuseven.untis.data.databases.entities.User
import com.sapuseven.untis.helpers.SerializationUtils.getJSON
import com.sapuseven.untis.helpers.timetable.TimetableDatabaseInterface
import com.sapuseven.untis.models.github.GithubUser
import com.sapuseven.untis.preferences.*
import com.sapuseven.untis.receivers.AutoMuteReceiver
Expand Down Expand Up @@ -630,6 +631,20 @@ class SettingsActivity : BaseComposeActivity() {
dataStore = dataStorePreferences.timetableRange
)

ElementPickerPreference(
title = { Text(stringResource(id = R.string.maindrawer_hide_subjects))},
icon = {
Icon(
painter = painterResource(id = R.drawable.all_hide_subjects),
contentDescription = null
)
},
type = TimetableDatabaseInterface.Type.SUBJECT,
multiSelect = true,
dataStore = dataStorePreferences.timetableHiddenElements,
timetableDatabaseInterface = timetableDatabaseInterface,
)

/*SwitchPreference(
title = { Text(stringResource(R.string.preference_timetable_range_index_reset)) },
dependency = dataStorePreferences.timetableRange,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,12 @@ val BaseComposeActivity.dataStorePreferences: DataStorePreferences
currentUserId(),
"infocenter_absences_timerange",
defaultValue = "current_schoolyear"
)
),
timetableHiddenElements = this.stringDataStore(
currentUserId(),
"timetable_hidden_elements",
defaultValue = ""
),
)
}

Expand Down Expand Up @@ -300,5 +305,6 @@ class DataStorePreferences(
val schoolBackground: UntisPreferenceDataStore<Set<String>>,
val infocenterAbsencesOnlyUnexcused: UntisPreferenceDataStore<Boolean>,
val infocenterAbsencesSortReverse: UntisPreferenceDataStore<Boolean>,
val infocenterAbsencesTimeRange: UntisPreferenceDataStore<String>
val infocenterAbsencesTimeRange: UntisPreferenceDataStore<String>,
val timetableHiddenElements: UntisPreferenceDataStore<String>,
)
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.google.accompanist.flowlayout.MainAxisAlignment
import com.sapuseven.untis.R
import com.sapuseven.untis.helpers.timetable.TimetableDatabaseInterface
import com.sapuseven.untis.models.untis.timetable.PeriodElement
Expand All @@ -48,7 +49,8 @@ fun ElementPickerDialogFullscreen(
onDismiss: (success: Boolean) -> Unit = {},
onSelect: (selectedItem: PeriodElement?) -> Unit = {},
onMultiSelect: (selectedItems: List<PeriodElement>) -> Unit = {},
additionalActions: (@Composable () -> Unit) = {}
additionalActions: (@Composable () -> Unit) = {},
selectedElements: List<PeriodElement>? = null,
) {
var selectedType by remember { mutableStateOf(initialType) }
var showSearch by remember { mutableStateOf(false) }
Expand All @@ -57,7 +59,9 @@ fun ElementPickerDialogFullscreen(
val items = remember(selectedType) {
mutableStateMapOf<PeriodElement, Boolean>().apply {
timetableDatabaseInterface.getElements(selectedType)
.associateWith { false }
.associateWith {
selectedElements?.contains(it) ?: false
}
.also {
putAll(it)
}
Expand Down Expand Up @@ -191,24 +195,30 @@ fun ElementPickerDialogFullscreen(

/**
* A minimal dialog version of the element picker.
* Missing features currently are: search, toolbar actions, multi select (due to missing confirm button), close button.
* Missing features currently are: toolbar actions, close button.
*/
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun ElementPickerDialog(
title: (@Composable () -> Unit)?,
timetableDatabaseInterface: TimetableDatabaseInterface,
initialType: TimetableDatabaseInterface.Type? = null,
multiSelect: Boolean,
hideTypeSelection: Boolean = false,
hideTypeSelectionPersonal: Boolean = false,
onDismiss: (success: Boolean) -> Unit = {},
onSelect: (selectedItem: PeriodElement?) -> Unit = {}
onSelect: (selectedItem: PeriodElement?) -> Unit = {},
onMultiSelect: (selectedItems: List<PeriodElement>) -> Unit,
initialSelection: List<PeriodElement>? = null,
) {
var selectedType by remember { mutableStateOf(initialType) }

val items = remember(selectedType) {
mutableStateMapOf<PeriodElement, Boolean>().apply {
timetableDatabaseInterface.getElements(selectedType)
.associateWith { false }
.associateWith {
initialSelection?.contains(it) ?: false
}
.also {
putAll(it)
}
Expand All @@ -235,12 +245,48 @@ fun ElementPickerDialog(
}
}

if (multiSelect) {
val interactionSource = remember { MutableInteractionSource() }
val selectAll: ((Boolean) -> Unit) = { selectState ->
items.forEach { (item, _) -> items[item] = selectState }
}

Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(horizontal = 24.dp)
) {
Checkbox(
checked = !items.values.contains(false),
onCheckedChange = selectAll,
interactionSource = interactionSource
)

Text(
text = stringResource(id = R.string.elementpicker_select_all),
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier
.clickable(
interactionSource = interactionSource,
indication = null,
role = Role.Checkbox
) {
selectAll(items.values.contains(false))
}
.weight(1f)
)
}

Divider()
}

ElementPickerElements(
timetableDatabaseInterface = timetableDatabaseInterface,
selectedType = selectedType,
onDismiss = onDismiss,
onSelect = onSelect,
items = items,
multiSelect = multiSelect,
modifier = Modifier
.fillMaxWidth()
.weight(1f)
Expand All @@ -255,6 +301,27 @@ fun ElementPickerDialog(
onDismiss = onDismiss,
onSelect = onSelect
)

if (multiSelect) {
FlowRow(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp, bottom = 24.dp)
.padding(horizontal = 24.dp),
) {
TextButton(onClick = {
onDismiss(false)
}) {
Text(text = stringResource(id = R.string.all_cancel))
}
TextButton(onClick = {
onMultiSelect(items.filter { it.value }.keys.toList())
onDismiss(true)
}) {
Text(text = stringResource(id = R.string.all_ok))
}
}
}
}
}
}
Expand All @@ -276,7 +343,7 @@ fun ElementPickerElements(
contentAlignment = Alignment.Center,
modifier = modifier
) {
selectedType?.let {
selectedType?.let { type ->
LazyVerticalGrid(
columns = GridCells.Adaptive(if (multiSelect) 128.dp else 96.dp),
modifier = Modifier.fillMaxHeight(),
Expand All @@ -288,7 +355,11 @@ fun ElementPickerElements(
object {
val element = it
val name = timetableDatabaseInterface.getShortName(it)
val enabled = timetableDatabaseInterface.isAllowed(it)
val enabled = if (type != TimetableDatabaseInterface.Type.SUBJECT) {
timetableDatabaseInterface.isAllowed(it)
} else {
true
}
}
}
.filter { it.name.contains(filter, true) }
Expand All @@ -303,7 +374,10 @@ fun ElementPickerElements(
if (multiSelect)
Checkbox(
checked = items[item.element] ?: false,
onCheckedChange = { items[item.element] = it },
onCheckedChange = {
onSelect(item.element)
items[item.element] = it
},
interactionSource = interactionSource,
enabled = item.enabled
)
Expand Down Expand Up @@ -370,10 +444,12 @@ fun ElementPickerTypeSelection(
contentDescription = null
)
},
label = { AbbreviatedText(
text = stringResource(id = R.string.all_personal),
abbreviatedText = stringResource(id = R.string.all_personal_abbr)
) },
label = {
AbbreviatedText(
text = stringResource(id = R.string.all_personal),
abbreviatedText = stringResource(id = R.string.all_personal_abbr)
)
},
selected = false,
onClick = {
onSelect(null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.sapuseven.untis.ui.preferences

import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.text.style.TextOverflow
import com.sapuseven.untis.helpers.SerializationUtils
import com.sapuseven.untis.helpers.timetable.TimetableDatabaseInterface
import com.sapuseven.untis.models.untis.timetable.PeriodElement
Expand All @@ -18,23 +19,32 @@ fun ElementPickerPreference(
dependency: UntisPreferenceDataStore<*>? = null,
dataStore: UntisPreferenceDataStore<String>,
timetableDatabaseInterface: TimetableDatabaseInterface,
highlight: Boolean = false
type: TimetableDatabaseInterface.Type? = null,
multiSelect: Boolean = false,
highlight: Boolean = false,
) {
val value = remember { mutableStateOf(dataStore.defaultValue) }
var showDialog by remember { mutableStateOf(false) }

val scope = rememberCoroutineScope()

@Composable
fun generateSummary(element: PeriodElement): String {
return timetableDatabaseInterface.getShortName(element)
fun generateSummary(elements: List<PeriodElement>): String = elements.joinToString { element ->
timetableDatabaseInterface.getShortName(element)
}

@Composable
fun summary(value: String) {
decodeStoredTimetableValue(value)?.let {
if (it.isNotEmpty()) {
Text(generateSummary(it), overflow = TextOverflow.Ellipsis, maxLines = 1)
}
}
}

Preference(
title = title,
summary = decodeStoredTimetableValue(value.value)?.let {
{ Text(generateSummary(it)) }
},
summary = { summary(value = value.value) },
icon = icon,
value = value,
dependency = dependency,
Expand All @@ -53,18 +63,36 @@ fun ElementPickerPreference(
showDialog = false
},
onSelect = { element ->
scope.launch { dataStore.saveValue(element?.let { encodeStoredTimetableValue(it) } ?: "") }
showDialog = false
if (!multiSelect) {
scope.launch {
dataStore.saveValue(element?.let { encodeStoredTimetableValue(listOf(it)) } ?: "")
}
showDialog = false
}
},
multiSelect = multiSelect,
onMultiSelect = { elements ->
scope.launch { dataStore.saveValue(encodeStoredTimetableValue(elements)) }
},
initialType = decodeStoredTimetableValue(value.value)?.let { TimetableDatabaseInterface.Type.valueOf(it.type) }
initialSelection = decodeStoredTimetableValue(value.value),
hideTypeSelection = type != null,
initialType = type ?:
decodeStoredTimetableValue(value.value)
?.firstOrNull()
?.let { TimetableDatabaseInterface.Type.valueOf(it.type)},
)
}

fun encodeStoredTimetableValue(value: PeriodElement): String =
SerializationUtils.getJSON().encodeToString(value)
fun encodeStoredTimetableValue(values: List<PeriodElement>): String =
SerializationUtils.getJSON().encodeToString(values)

fun decodeStoredTimetableValue(value: String): PeriodElement? = try {
SerializationUtils.getJSON().decodeFromString(value)
fun decodeStoredTimetableValue(values: String): List<PeriodElement>? = try {
SerializationUtils.getJSON().decodeFromString(values)
} catch (e: Throwable) {
null
// Backwards-compatibility when the value isn't stored as a list yet
try {
listOf(SerializationUtils.getJSON().decodeFromString(values))
} catch (e: Throwable) {
null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ import com.sapuseven.untis.data.databases.UserDatabase
import com.sapuseven.untis.data.databases.entities.User
import com.sapuseven.untis.helpers.config.booleanDataStore
import com.sapuseven.untis.helpers.config.intDataStore
import com.sapuseven.untis.helpers.config.stringDataStore
import com.sapuseven.untis.helpers.timetable.TimetableDatabaseInterface
import com.sapuseven.untis.receivers.AutoMuteReceiver
import com.sapuseven.untis.receivers.AutoMuteReceiver.Companion.EXTRA_BOOLEAN_MUTE
import com.sapuseven.untis.receivers.AutoMuteReceiver.Companion.EXTRA_INT_ID
import com.sapuseven.untis.receivers.AutoMuteReceiver.Companion.EXTRA_LONG_USER_ID
import com.sapuseven.untis.ui.preferences.decodeStoredTimetableValue
import com.sapuseven.untis.workers.DailyWorker.Companion.WORKER_DATA_USER_ID
import org.joda.time.LocalDateTime

Expand Down Expand Up @@ -81,6 +83,13 @@ class AutoMuteSetupWorker(context: Context, params: WorkerParameters) :
"preference_automute_minimum_break_length"
).getValue()

val hiddenSubjects = decodeStoredTimetableValue(
applicationContext.stringDataStore(
user.id,
"timetable_hidden_elements"
).getValue()
).orEmpty()

timetable.items.merged().sortedBy { it.startDateTime }.zipWithNext().withLast()
.forEach {
it.first?.let { item ->
Expand All @@ -94,6 +103,9 @@ class AutoMuteSetupWorker(context: Context, params: WorkerParameters) :
if (item.periodData.isCancelled() && !automuteCancelledLessons)
return@forEach // lesson is cancelled

if (item.periodData.subjects.isNotEmpty() && hiddenSubjects.containsAll(item.periodData.subjects))
return@forEach // lesson is hidden

val muteIntent =
Intent(applicationContext, AutoMuteReceiver::class.java)
.putExtra(EXTRA_INT_ID, id)
Expand Down
Loading