forked from github/wulkanowy-mirror
add: custom attendance percentage
This commit is contained in:
parent
c1687f5856
commit
4d74c252c8
@ -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.
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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()
|
||||||
|
@ -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,
|
||||||
),
|
),
|
||||||
|
@ -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()
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
4
app/src/main/res/values/integers.xml
Normal file
4
app/src/main/res/values/integers.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<integer name="pref_default_attendance_percentage">-1</integer>
|
||||||
|
</resources>
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user