add: custom attendance percentage

This commit is contained in:
sadorowo 2024-02-29 09:40:29 +01:00
parent c1687f5856
commit 4d74c252c8
18 changed files with 105 additions and 35 deletions

View File

@ -6,7 +6,7 @@
* hide bad grades (1, 1+, 2, 2+, 2-) * hide bad grades (1, 1+, 2, 2+, 2-)
* hide bad attendance (everything except present/excused attendance) * hide bad attendance (everything except present/excused attendance)
* hide comments * hide comments
* fake attendance (always 100%) * fake attendance
To get to the hidden panel, go to the "More" tab and then long-press the "Settings" tile. To get to the hidden panel, go to the "More" tab and then long-press the "Settings" tile.

View File

@ -2,6 +2,8 @@ package io.github.wulkanowy.data.repositories
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.core.content.edit import androidx.core.content.edit
import com.fredporciuncula.flow.preferences.FlowSharedPreferences import com.fredporciuncula.flow.preferences.FlowSharedPreferences
@ -283,6 +285,18 @@ class PreferencesRepository @Inject constructor(
selectedHiddenSettingTilesPreference.set(filteredValue) selectedHiddenSettingTilesPreference.set(filteredValue)
} }
var attendancePercentage: Float?
get() {
val value = attendancePercentagePreference.get()
return if (value == context.resources.getInteger(R.integer.pref_default_attendance_percentage).toFloat()) {
null
} else {
value
}
}
set(value) = value?.let { attendancePercentagePreference.set(it) }
?: attendancePercentagePreference.delete()
private val selectedDashboardTilesPreference: Preference<Set<String>> private val selectedDashboardTilesPreference: Preference<Set<String>>
get() { get() {
val defaultSet = val defaultSet =
@ -301,6 +315,12 @@ class PreferencesRepository @Inject constructor(
return flowSharedPref.getStringSet(prefKey, defaultSet) return flowSharedPref.getStringSet(prefKey, defaultSet)
} }
private val attendancePercentagePreference: Preference<Float>
get() = flowSharedPref.getFloat(
context.getString(R.string.pref_key_attendance_percentage),
context.resources.getInteger(R.integer.pref_default_attendance_percentage).toFloat()
)
var dismissedAdminMessageIds: List<Int> var dismissedAdminMessageIds: List<Int>
get() = sharedPref.getStringSet(PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS, emptySet()) get() = sharedPref.getStringSet(PREF_KEY_ADMIN_DISMISSED_MESSAGE_IDS, emptySet())
.orEmpty() .orEmpty()

View File

@ -8,7 +8,6 @@ import io.github.wulkanowy.data.db.entities.AttendanceSummary
import io.github.wulkanowy.data.repositories.PreferencesRepository import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.ItemAttendanceSummaryBinding import io.github.wulkanowy.databinding.ItemAttendanceSummaryBinding
import io.github.wulkanowy.databinding.ScrollableHeaderAttendanceSummaryBinding import io.github.wulkanowy.databinding.ScrollableHeaderAttendanceSummaryBinding
import io.github.wulkanowy.ui.modules.dashboard.DashboardItem
import io.github.wulkanowy.utils.calculatePercentage import io.github.wulkanowy.utils.calculatePercentage
import io.github.wulkanowy.utils.getFormattedName import io.github.wulkanowy.utils.getFormattedName
import java.time.Month import java.time.Month
@ -20,9 +19,7 @@ class AttendanceSummaryAdapter @Inject constructor(
) : ) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() { RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val fake = preferencesRepository private val attendancePercentage = preferencesRepository.attendancePercentage
.selectedHiddenSettingTiles
.contains(DashboardItem.HiddenSettingTile.ATTENDANCE)
private enum class ViewType(val id: Int) { private enum class ViewType(val id: Int) {
HEADER(1), HEADER(1),
@ -56,7 +53,10 @@ class AttendanceSummaryAdapter @Inject constructor(
} }
private fun bindHeaderViewHolder(binding: ScrollableHeaderAttendanceSummaryBinding) { private fun bindHeaderViewHolder(binding: ScrollableHeaderAttendanceSummaryBinding) {
binding.attendanceSummaryScrollableHeaderPercentage.text = formatPercentage(items.calculatePercentage(fake)) binding.attendanceSummaryScrollableHeaderPercentage.text = formatPercentage(
attendancePercentage?.toDouble() ?:
items.calculatePercentage()
)
} }
private fun bindItemViewHolder(binding: ItemAttendanceSummaryBinding, position: Int) { private fun bindItemViewHolder(binding: ItemAttendanceSummaryBinding, position: Int) {
@ -68,8 +68,8 @@ class AttendanceSummaryAdapter @Inject constructor(
else -> item.month.getFormattedName() else -> item.month.getFormattedName()
} }
attendanceSummaryPercentage.text = when (position) { attendanceSummaryPercentage.text = when (position) {
-1 -> formatPercentage(items.calculatePercentage(fake)) -1 -> formatPercentage(attendancePercentage?.toDouble() ?: item.calculatePercentage())
else -> formatPercentage(item.calculatePercentage(fake)) else -> formatPercentage(attendancePercentage?.toDouble() ?: item.calculatePercentage())
} }
attendanceSummaryPresent.text = item.presence.toString() attendanceSummaryPresent.text = item.presence.toString()

View File

@ -284,9 +284,7 @@ class DashboardPresenter @Inject constructor(
private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) { private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) {
flow { flow {
val fake = preferencesRepository val attendancePercentage = preferencesRepository.attendancePercentage
.selectedHiddenSettingTiles
.contains(DashboardItem.HiddenSettingTile.ATTENDANCE)
val selectedTiles = selectedDashboardTiles val selectedTiles = selectedDashboardTiles
val flowSuccess = flowOf(Resource.Success(null)) val flowSuccess = flowOf(Resource.Success(null))
@ -339,7 +337,7 @@ class DashboardPresenter @Inject constructor(
} else null } else null
}, },
attendancePercentage = DashboardItem.HorizontalGroup.Cell( attendancePercentage = DashboardItem.HorizontalGroup.Cell(
data = attendanceResource.dataOrNull?.calculatePercentage(fake), data = attendancePercentage?.toDouble() ?: attendanceResource.dataOrNull?.calculatePercentage(),
error = attendanceResource.errorOrNull != null, error = attendanceResource.errorOrNull != null,
isLoading = attendanceResource is Resource.Loading, isLoading = attendanceResource is Resource.Loading,
), ),

View File

@ -1,12 +1,17 @@
package io.github.wulkanowy.ui.modules.more package io.github.wulkanowy.ui.modules.more
import android.os.Bundle import android.os.Bundle
import android.text.InputFilter
import android.text.InputType
import android.text.Spanned
import android.view.View import android.view.View
import android.widget.EditText
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.repositories.PreferencesRepository
import io.github.wulkanowy.databinding.FragmentMoreBinding import io.github.wulkanowy.databinding.FragmentMoreBinding
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.Destination import io.github.wulkanowy.ui.modules.Destination
@ -17,6 +22,24 @@ import io.github.wulkanowy.ui.modules.message.MessageFragment
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
private class AttendancePercentageFilter : InputFilter {
override fun filter(
source: CharSequence?,
start: Int,
end: Int,
dest: Spanned?,
dstart: Int,
dend: Int
): CharSequence? {
val input = dest.toString() + source.toString()
val floatRepresentation = input.toFloatOrNull()
if (floatRepresentation != null && floatRepresentation in 0.0..100.0) return null
return ""
}
}
@AndroidEntryPoint @AndroidEntryPoint
class MoreFragment : BaseFragment<FragmentMoreBinding>(R.layout.fragment_more), MoreView, class MoreFragment : BaseFragment<FragmentMoreBinding>(R.layout.fragment_more), MoreView,
MainView.TitledView, MainView.MainChildView { MainView.TitledView, MainView.MainChildView {
@ -27,6 +50,9 @@ class MoreFragment : BaseFragment<FragmentMoreBinding>(R.layout.fragment_more),
@Inject @Inject
lateinit var moreAdapter: MoreAdapter lateinit var moreAdapter: MoreAdapter
@Inject
lateinit var preferencesRepository: PreferencesRepository
companion object { companion object {
fun newInstance() = MoreFragment() fun newInstance() = MoreFragment()
} }
@ -80,19 +106,44 @@ class MoreFragment : BaseFragment<FragmentMoreBinding>(R.layout.fragment_more),
} }
override fun showHiddenSettings(data: List<DashboardItem.HiddenSettingTile>) { override fun showHiddenSettings(data: List<DashboardItem.HiddenSettingTile>) {
val badGradesEntries = requireContext().resources.getStringArray(R.array.hidden_settings_bad_grades_entries)
val entries = requireContext().resources.getStringArray(R.array.hidden_settings_entries) val entries = requireContext().resources.getStringArray(R.array.hidden_settings_entries)
val values = requireContext().resources.getStringArray(R.array.hidden_settings_values) val values = requireContext().resources.getStringArray(R.array.hidden_settings_values)
val selectedItemsState = values.map { value -> data.any { it.name == value } } val selectedItemsState = values.map { value -> data.any { it.name == value } }
val attendancePercentage = preferencesRepository.attendancePercentage
val badGrades = preferencesRepository.badGrades
val input = EditText(requireContext()).apply {
setPadding(40, 20, 40, 20)
hint = requireContext().getString(R.string.pref_hidden_settings_hint)
filters = arrayOf(AttendancePercentageFilter())
inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL
}
// enable only if attendance modifier is enabled
val attendanceModifierEnabled = data.any { it == DashboardItem.HiddenSettingTile.ATTENDANCE }
input.isEnabled = attendanceModifierEnabled
if (attendancePercentage != null) input.setText(attendancePercentage.toString())
MaterialAlertDialogBuilder(requireContext()) MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.pref_hidden_settings_title) .setTitle(R.string.pref_hidden_settings_title)
.setMultiChoiceItems(entries, selectedItemsState.toBooleanArray()) { _, _, _ -> } .setMultiChoiceItems(entries, selectedItemsState.toBooleanArray()) { dialog, index, _ ->
// if attendance modifier is enabled, enable text input
if (index == values.indexOf(DashboardItem.HiddenSettingTile.ATTENDANCE.name))
input.isEnabled = (dialog as AlertDialog).listView.checkedItemPositions[index]
}
.setView(input)
.setPositiveButton(android.R.string.ok) { dialog, _ -> .setPositiveButton(android.R.string.ok) { dialog, _ ->
val selectedState = (dialog as AlertDialog).listView.checkedItemPositions val selectedState = (dialog as AlertDialog).listView.checkedItemPositions
val selectedValues = values.filterIndexed { index, _ -> selectedState[index] } val selectedValues = values.filterIndexed { index, _ -> selectedState[index] }
val inputAttendancePercentage = selectedValues
.find { it == DashboardItem.HiddenSettingTile.ATTENDANCE.name }
?.let { input.text.toString().toFloatOrNull() }
Timber.i("Selected hidden settings: $selectedValues") Timber.i("Selected hidden settings: $selectedValues")
presenter.onHiddenSettingsSelected(selectedValues) presenter.onHiddenSettingsSelected(selectedValues, inputAttendancePercentage)
} }
.setNegativeButton(android.R.string.cancel) { _, _ -> } .setNegativeButton(android.R.string.cancel) { _, _ -> }
.show() .show()

View File

@ -45,11 +45,13 @@ class MorePresenter @Inject constructor(
} }
} }
fun onHiddenSettingsSelected(selectedItems: List<String>) { fun onHiddenSettingsSelected(selectedItems: List<String>, attendance: Float?) {
preferencesRepository.selectedHiddenSettingTiles = selectedItems.map { preferencesRepository.selectedHiddenSettingTiles = selectedItems.map {
DashboardItem.HiddenSettingTile.valueOf(it) DashboardItem.HiddenSettingTile.valueOf(it)
} }
preferencesRepository.attendancePercentage = attendance
view?.restartApp() view?.restartApp()
} }

View File

@ -19,18 +19,10 @@ private inline val AttendanceSummary.allAbsences: Double
inline val Attendance.isExcusableOrNotExcused: Boolean inline val Attendance.isExcusableOrNotExcused: Boolean
get() = (excusable || ((absence || lateness) && !excused)) && excuseStatus == null get() = (excusable || ((absence || lateness) && !excused)) && excuseStatus == null
fun AttendanceSummary.calculatePercentage(fake: Boolean = false) = if (fake) { fun AttendanceSummary.calculatePercentage() = calculatePercentage(allPresences, allAbsences)
100.0
} else {
calculatePercentage(allPresences, allAbsences)
}
fun List<AttendanceSummary>.calculatePercentage(fake: Boolean = false): Double { fun List<AttendanceSummary>.calculatePercentage(): Double {
return if (fake) { return calculatePercentage(sumOf { it.allPresences }, sumOf { it.allAbsences })
100.0
} else {
calculatePercentage(sumOf { it.allPresences }, sumOf { it.allAbsences })
}
} }
private fun calculatePercentage(presence: Double, absence: Double): Double { private fun calculatePercentage(presence: Double, absence: Double): Double {

View File

@ -72,6 +72,6 @@
<item>Špatné hodnocení</item> <item>Špatné hodnocení</item>
<item>Špatná účast</item> <item>Špatná účast</item>
<item>Poznámky</item> <item>Poznámky</item>
<item>Falešná 100% účast</item> <item>Falešná účast</item>
</string-array> </string-array>
</resources> </resources>

View File

@ -71,6 +71,6 @@
<item>Bad grades</item> <item>Bad grades</item>
<item>Bad attendance</item> <item>Bad attendance</item>
<item>Notes</item> <item>Notes</item>
<item>Fake 100% attendance</item> <item>Fake attendance</item>
</string-array> </string-array>
</resources> </resources>

View File

@ -71,6 +71,6 @@
<item>Bad grades</item> <item>Bad grades</item>
<item>Bad attendance</item> <item>Bad attendance</item>
<item>Notes</item> <item>Notes</item>
<item>Fake 100% attendance</item> <item>Fake attendance</item>
</string-array> </string-array>
</resources> </resources>

View File

@ -71,6 +71,6 @@
<item>Bad grades</item> <item>Bad grades</item>
<item>Bad attendance</item> <item>Bad attendance</item>
<item>Notes</item> <item>Notes</item>
<item>Fake 100% attendance</item> <item>Fake attendance</item>
</string-array> </string-array>
</resources> </resources>

View File

@ -71,6 +71,6 @@
<item>Słabe oceny</item> <item>Słabe oceny</item>
<item>Słaba frekwencja</item> <item>Słaba frekwencja</item>
<item>Uwagi</item> <item>Uwagi</item>
<item>Fałszywa 100% frekwencja</item> <item>Fałszywa frekwencja</item>
</string-array> </string-array>
</resources> </resources>

View File

@ -71,6 +71,6 @@
<item>Плохие оценки</item> <item>Плохие оценки</item>
<item>Плохая посещаемость</item> <item>Плохая посещаемость</item>
<item>Примечания</item> <item>Примечания</item>
<item>Фейковая 100% посещаемость</item> <item>Фейковая посещаемость</item>
</string-array> </string-array>
</resources> </resources>

View File

@ -71,6 +71,6 @@
<item>Zlé známky</item> <item>Zlé známky</item>
<item>Zlá účasť</item> <item>Zlá účasť</item>
<item>Poznámky</item> <item>Poznámky</item>
<item>Falošná 100% účasť</item> <item>Falošná účasť</item>
</string-array> </string-array>
</resources> </resources>

View File

@ -71,6 +71,6 @@
<item>Погані оцінки</item> <item>Погані оцінки</item>
<item>Погана відвідуваність</item> <item>Погана відвідуваність</item>
<item>Примітки</item> <item>Примітки</item>
<item>Фальшива 100% явка</item> <item>Фальшива явка</item>
</string-array> </string-array>
</resources> </resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="pref_default_attendance_percentage">-1</integer>
</resources>

View File

@ -161,7 +161,7 @@
<item>Bad grades</item> <item>Bad grades</item>
<item>Bad attendance</item> <item>Bad attendance</item>
<item>Notes</item> <item>Notes</item>
<item>Fake 100% attendance</item> <item>Fake attendance</item>
</string-array> </string-array>
<string-array name="hidden_settings_values" translatable="false"> <string-array name="hidden_settings_values" translatable="false">
<item>BAD_GRADES</item> <item>BAD_GRADES</item>

View File

@ -865,4 +865,7 @@
<string name="error_feature_disabled">Feature disabled by your school</string> <string name="error_feature_disabled">Feature disabled by your school</string>
<string name="error_feature_not_available">Feature not available. Login in a mode other than Mobile API</string> <string name="error_feature_not_available">Feature not available. Login in a mode other than Mobile API</string>
<string name="error_field_required">This field is required</string> <string name="error_field_required">This field is required</string>
<string name="pref_hidden_settings_hint">Attendance percentage</string>
<string name="pref_key_attendance_percentage">attendance_percentage</string>
<string name="pref_hidden_settings_bad_grades_title">Bad grades</string>
</resources> </resources>