forked from github/wulkanowy-mirror
Merge branch 'release/1.2.1'
This commit is contained in:
commit
3d0dcead50
@ -21,8 +21,8 @@ android {
|
||||
testApplicationId "io.github.tests.wulkanowy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
versionCode 93
|
||||
versionName "1.2.0"
|
||||
versionCode 94
|
||||
versionName "1.2.1"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
|
||||
@ -133,7 +133,7 @@ play {
|
||||
serviceAccountEmail = System.getenv("PLAY_SERVICE_ACCOUNT_EMAIL") ?: "jan@fakelog.cf"
|
||||
serviceAccountCredentials = file('key.p12')
|
||||
defaultToAppBundles = false
|
||||
track = 'beta'
|
||||
track = 'production'
|
||||
updatePriority = 3
|
||||
}
|
||||
|
||||
@ -157,11 +157,11 @@ ext {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "io.github.wulkanowy:sdk:1.2.0"
|
||||
implementation "io.github.wulkanowy:sdk:1.2.1"
|
||||
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
|
||||
|
||||
implementation "androidx.core:core-ktx:1.6.0"
|
||||
implementation "androidx.activity:activity-ktx:1.3.1"
|
||||
@ -215,10 +215,10 @@ dependencies {
|
||||
playImplementation 'com.google.firebase:firebase-analytics-ktx'
|
||||
playImplementation 'com.google.firebase:firebase-messaging:'
|
||||
playImplementation 'com.google.firebase:firebase-crashlytics:'
|
||||
playImplementation 'com.google.android.play:core:1.10.0'
|
||||
playImplementation 'com.google.android.play:core:1.10.1'
|
||||
playImplementation 'com.google.android.play:core-ktx:1.8.1'
|
||||
|
||||
hmsImplementation 'com.huawei.hms:hianalytics:6.1.1.300'
|
||||
hmsImplementation 'com.huawei.hms:hianalytics:6.2.0.301'
|
||||
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.6.0.300'
|
||||
|
||||
releaseImplementation "com.github.ChuckerTeam.Chucker:library-no-op:$chucker"
|
||||
@ -228,7 +228,7 @@ dependencies {
|
||||
|
||||
testImplementation "junit:junit:4.13.2"
|
||||
testImplementation "io.mockk:mockk:$mockk"
|
||||
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.1'
|
||||
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.2'
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||
|
||||
testImplementation 'org.robolectric:robolectric:4.6.1'
|
||||
|
@ -33,10 +33,16 @@ class GradeRepository @Inject constructor(
|
||||
|
||||
private val cacheKey = "grade"
|
||||
|
||||
fun getGrades(student: Student, semester: Semester, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
||||
fun getGrades(
|
||||
student: Student,
|
||||
semester: Semester,
|
||||
forceRefresh: Boolean,
|
||||
notify: Boolean = false
|
||||
) = networkBoundResource(
|
||||
mutex = saveFetchResultMutex,
|
||||
shouldFetch = { (details, summaries) ->
|
||||
val isShouldBeRefreshed = refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
val isShouldBeRefreshed =
|
||||
refreshHelper.isShouldBeRefreshed(getRefreshKey(cacheKey, semester))
|
||||
details.isEmpty() || summaries.isEmpty() || forceRefresh || isShouldBeRefreshed
|
||||
},
|
||||
query = {
|
||||
@ -59,8 +65,14 @@ class GradeRepository @Inject constructor(
|
||||
}
|
||||
)
|
||||
|
||||
private suspend fun refreshGradeDetails(student: Student, oldGrades: List<Grade>, newDetails: List<Grade>, notify: Boolean) {
|
||||
val notifyBreakDate = oldGrades.maxByOrNull { it.date }?.date ?: student.registrationDate.toLocalDate()
|
||||
private suspend fun refreshGradeDetails(
|
||||
student: Student,
|
||||
oldGrades: List<Grade>,
|
||||
newDetails: List<Grade>,
|
||||
notify: Boolean
|
||||
) {
|
||||
val notifyBreakDate =
|
||||
oldGrades.maxByOrNull { it.date }?.date ?: student.registrationDate.toLocalDate()
|
||||
gradeDb.deleteAll(oldGrades uniqueSubtract newDetails)
|
||||
gradeDb.insertAll((newDetails uniqueSubtract oldGrades).onEach {
|
||||
if (it.date >= notifyBreakDate) it.apply {
|
||||
@ -70,10 +82,15 @@ class GradeRepository @Inject constructor(
|
||||
})
|
||||
}
|
||||
|
||||
private suspend fun refreshGradeSummaries(oldSummaries: List<GradeSummary>, newSummary: List<GradeSummary>, notify: Boolean) {
|
||||
private suspend fun refreshGradeSummaries(
|
||||
oldSummaries: List<GradeSummary>,
|
||||
newSummary: List<GradeSummary>,
|
||||
notify: Boolean
|
||||
) {
|
||||
gradeSummaryDb.deleteAll(oldSummaries uniqueSubtract newSummary)
|
||||
gradeSummaryDb.insertAll((newSummary uniqueSubtract oldSummaries).onEach { summary ->
|
||||
val oldSummary = oldSummaries.find { oldSummary -> oldSummary.subject == summary.subject }
|
||||
val oldSummary =
|
||||
oldSummaries.find { oldSummary -> oldSummary.subject == summary.subject }
|
||||
summary.isPredictedGradeNotified = when {
|
||||
summary.predictedGrade.isEmpty() -> true
|
||||
notify && oldSummary?.predictedGrade != summary.predictedGrade -> false
|
||||
|
@ -22,7 +22,11 @@ class SemesterRepository @Inject constructor(
|
||||
private val dispatchers: DispatchersProvider
|
||||
) {
|
||||
|
||||
suspend fun getSemesters(student: Student, forceRefresh: Boolean = false, refreshOnNoCurrent: Boolean = false) = withContext(dispatchers.backgroundThread) {
|
||||
suspend fun getSemesters(
|
||||
student: Student,
|
||||
forceRefresh: Boolean = false,
|
||||
refreshOnNoCurrent: Boolean = false
|
||||
) = withContext(dispatchers.backgroundThread) {
|
||||
val semesters = semesterDb.loadAll(student.studentId, student.classId)
|
||||
|
||||
if (isShouldFetch(student, semesters, forceRefresh, refreshOnNoCurrent)) {
|
||||
@ -31,14 +35,21 @@ class SemesterRepository @Inject constructor(
|
||||
} else semesters
|
||||
}
|
||||
|
||||
private fun isShouldFetch(student: Student, semesters: List<Semester>, forceRefresh: Boolean, refreshOnNoCurrent: Boolean): Boolean {
|
||||
private fun isShouldFetch(
|
||||
student: Student,
|
||||
semesters: List<Semester>,
|
||||
forceRefresh: Boolean,
|
||||
refreshOnNoCurrent: Boolean
|
||||
): Boolean {
|
||||
val isNoSemesters = semesters.isEmpty()
|
||||
|
||||
val isRefreshOnModeChangeRequired = if (Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||
semesters.firstOrNull { it.isCurrent }?.diaryId == 0
|
||||
} else false
|
||||
val isRefreshOnModeChangeRequired =
|
||||
if (Sdk.Mode.valueOf(student.loginMode) != Sdk.Mode.API) {
|
||||
semesters.firstOrNull { it.isCurrent }?.diaryId == 0
|
||||
} else false
|
||||
|
||||
val isRefreshOnNoCurrentAppropriate = refreshOnNoCurrent && !semesters.any { semester -> semester.isCurrent }
|
||||
val isRefreshOnNoCurrentAppropriate =
|
||||
refreshOnNoCurrent && !semesters.any { semester -> semester.isCurrent }
|
||||
|
||||
return forceRefresh || isNoSemesters || isRefreshOnModeChangeRequired || isRefreshOnNoCurrentAppropriate
|
||||
}
|
||||
@ -52,7 +63,8 @@ class SemesterRepository @Inject constructor(
|
||||
semesterDb.insertSemesters(new.uniqueSubtract(old))
|
||||
}
|
||||
|
||||
suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) = withContext(dispatchers.backgroundThread) {
|
||||
getSemesters(student, forceRefresh).getCurrentOrLast()
|
||||
}
|
||||
suspend fun getCurrentSemester(student: Student, forceRefresh: Boolean = false) =
|
||||
withContext(dispatchers.backgroundThread) {
|
||||
getSemesters(student, forceRefresh).getCurrentOrLast()
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ open class BasePresenter<T : BaseView>(
|
||||
protected val studentRepository: StudentRepository
|
||||
) : CoroutineScope {
|
||||
|
||||
private var job: Job = Job()
|
||||
private var job = Job()
|
||||
|
||||
private val jobs = mutableMapOf<String, Job>()
|
||||
|
||||
|
@ -8,7 +8,7 @@ import androidx.core.view.get
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.databinding.FragmentAccountBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment
|
||||
@ -75,9 +75,7 @@ class AccountFragment : BaseFragment<FragmentAccountBinding>(R.layout.fragment_a
|
||||
}
|
||||
}
|
||||
|
||||
override fun openAccountDetailsView(studentWithSemesters: StudentWithSemesters) {
|
||||
(activity as? MainActivity)?.pushView(
|
||||
AccountDetailsFragment.newInstance(studentWithSemesters)
|
||||
)
|
||||
override fun openAccountDetailsView(student: Student) {
|
||||
(activity as? MainActivity)?.pushView(AccountDetailsFragment.newInstance(student))
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ class AccountPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
fun onItemSelected(studentWithSemesters: StudentWithSemesters) {
|
||||
view?.openAccountDetailsView(studentWithSemesters)
|
||||
view?.openAccountDetailsView(studentWithSemesters.student)
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
|
@ -1,6 +1,6 @@
|
||||
package io.github.wulkanowy.ui.modules.account
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
|
||||
interface AccountView : BaseView {
|
||||
@ -11,5 +11,5 @@ interface AccountView : BaseView {
|
||||
|
||||
fun openLoginView()
|
||||
|
||||
fun openAccountDetailsView(studentWithSemesters: StudentWithSemesters)
|
||||
fun openAccountDetailsView(student: Student)
|
||||
}
|
||||
|
@ -37,9 +37,9 @@ class AccountDetailsFragment :
|
||||
|
||||
private const val ARGUMENT_KEY = "Data"
|
||||
|
||||
fun newInstance(studentWithSemesters: StudentWithSemesters) =
|
||||
fun newInstance(student: Student) =
|
||||
AccountDetailsFragment().apply {
|
||||
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, studentWithSemesters) }
|
||||
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, student) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ class AccountDetailsFragment :
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding = FragmentAccountDetailsBinding.bind(view)
|
||||
presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as StudentWithSemesters)
|
||||
presenter.onAttachView(this, requireArguments()[ARGUMENT_KEY] as Student)
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
|
@ -2,6 +2,7 @@ package io.github.wulkanowy.ui.modules.account.accountdetails
|
||||
|
||||
import io.github.wulkanowy.data.Resource
|
||||
import io.github.wulkanowy.data.Status
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.StudentWithSemesters
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.services.sync.SyncManager
|
||||
@ -27,9 +28,9 @@ class AccountDetailsPresenter @Inject constructor(
|
||||
|
||||
private var studentId: Long? = null
|
||||
|
||||
fun onAttachView(view: AccountDetailsView, studentWithSemesters: StudentWithSemesters) {
|
||||
fun onAttachView(view: AccountDetailsView, student: Student) {
|
||||
super.onAttachView(view)
|
||||
studentId = studentWithSemesters.student.id
|
||||
studentId = student.id
|
||||
|
||||
view.initView()
|
||||
errorHandler.showErrorMessage = ::showErrorViewOnError
|
||||
|
@ -14,6 +14,7 @@ import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.Timetable
|
||||
import io.github.wulkanowy.data.db.entities.TimetableHeader
|
||||
import io.github.wulkanowy.databinding.ItemDashboardAccountBinding
|
||||
@ -41,7 +42,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
|
||||
private var lessonsTimer: Timer? = null
|
||||
|
||||
var onAccountTileClickListener: () -> Unit = {}
|
||||
var onAccountTileClickListener: (Student) -> Unit = {}
|
||||
|
||||
var onLuckyNumberTileClickListener: () -> Unit = {}
|
||||
|
||||
@ -152,7 +153,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
dashboardAccountItemName.text = student?.nickOrName.orEmpty()
|
||||
dashboardAccountItemSchoolName.text = student?.schoolName.orEmpty()
|
||||
|
||||
root.setOnClickListener { onAccountTileClickListener() }
|
||||
root.setOnClickListener { student?.let(onAccountTileClickListener) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -170,38 +171,41 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
val binding = horizontalGroupViewHolder.binding
|
||||
val context = binding.root.context
|
||||
val attendanceColor = when {
|
||||
attendancePercentage ?: 0.0 <= ATTENDANCE_SECOND_WARNING_THRESHOLD -> {
|
||||
attendancePercentage == null || attendancePercentage == .0 -> {
|
||||
context.getThemeAttrColor(R.attr.colorOnSurface)
|
||||
}
|
||||
attendancePercentage <= ATTENDANCE_SECOND_WARNING_THRESHOLD -> {
|
||||
context.getThemeAttrColor(R.attr.colorPrimary)
|
||||
}
|
||||
attendancePercentage ?: 0.0 <= ATTENDANCE_FIRST_WARNING_THRESHOLD -> {
|
||||
attendancePercentage <= ATTENDANCE_FIRST_WARNING_THRESHOLD -> {
|
||||
context.getThemeAttrColor(R.attr.colorTimetableChange)
|
||||
}
|
||||
else -> context.getThemeAttrColor(R.attr.colorOnSurface)
|
||||
}
|
||||
val attendanceString = if (attendancePercentage == null || attendancePercentage == .0) {
|
||||
context.getString(R.string.dashboard_horizontal_group_no_data)
|
||||
} else {
|
||||
"%.2f%%".format(attendancePercentage)
|
||||
}
|
||||
|
||||
with(binding.dashboardHorizontalGroupItemAttendanceValue) {
|
||||
text = "%.2f%%".format(attendancePercentage)
|
||||
text = attendanceString
|
||||
setTextColor(attendanceColor)
|
||||
}
|
||||
|
||||
with(binding) {
|
||||
dashboardHorizontalGroupItemMessageValue.text = unreadMessagesCount.toString()
|
||||
dashboardHorizontalGroupItemLuckyValue.text = if (luckyNumber == -1) {
|
||||
context.getString(R.string.dashboard_horizontal_group_no_lukcy_number)
|
||||
dashboardHorizontalGroupItemLuckyValue.text = if (luckyNumber == 0) {
|
||||
context.getString(R.string.dashboard_horizontal_group_no_data)
|
||||
} else luckyNumber?.toString()
|
||||
|
||||
if (dashboardHorizontalGroupItemInfoContainer.isVisible != (error != null || isLoading)) {
|
||||
dashboardHorizontalGroupItemInfoContainer.isVisible = error != null || isLoading
|
||||
}
|
||||
|
||||
if (dashboardHorizontalGroupItemInfoProgress.isVisible != isLoading) {
|
||||
dashboardHorizontalGroupItemInfoProgress.isVisible = isLoading
|
||||
}
|
||||
|
||||
dashboardHorizontalGroupItemInfoContainer.isVisible = error != null || isLoading
|
||||
dashboardHorizontalGroupItemInfoProgress.isVisible =
|
||||
(isLoading && !item.isDataLoaded) || (isLoading && !item.isFullDataLoaded)
|
||||
dashboardHorizontalGroupItemInfoErrorText.isVisible = error != null
|
||||
|
||||
with(dashboardHorizontalGroupItemLuckyContainer) {
|
||||
isVisible = error == null && !isLoading && luckyNumber != null
|
||||
isVisible = luckyNumber != null && luckyNumber != -1
|
||||
setOnClickListener { onLuckyNumberTileClickListener() }
|
||||
|
||||
updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
@ -216,7 +220,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
}
|
||||
|
||||
with(dashboardHorizontalGroupItemAttendanceContainer) {
|
||||
isVisible = error == null && !isLoading && attendancePercentage != null
|
||||
isVisible = attendancePercentage != null && attendancePercentage != -1.0
|
||||
updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
matchConstraintPercentWidth = when {
|
||||
luckyNumber == null && unreadMessagesCount == null -> 1.0f
|
||||
@ -228,7 +232,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
}
|
||||
|
||||
with(dashboardHorizontalGroupItemMessageContainer) {
|
||||
isVisible = error == null && !isLoading && unreadMessagesCount != null
|
||||
isVisible = unreadMessagesCount != null && unreadMessagesCount != -1
|
||||
setOnClickListener { onMessageTileClickListener() }
|
||||
}
|
||||
}
|
||||
@ -291,14 +295,14 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
updateLessonView(item, currentTimetable, binding)
|
||||
binding.dashboardLessonsItemTitleTomorrow.isVisible = false
|
||||
}
|
||||
currentDayHeader != null && currentDayHeader.content.isNotBlank() -> {
|
||||
updateLessonView(item, emptyList(), binding, currentDayHeader)
|
||||
binding.dashboardLessonsItemTitleTomorrow.isVisible = false
|
||||
}
|
||||
tomorrowTimetable.isNotEmpty() -> {
|
||||
updateLessonView(item, tomorrowTimetable, binding)
|
||||
binding.dashboardLessonsItemTitleTomorrow.isVisible = true
|
||||
}
|
||||
currentDayHeader != null && currentDayHeader.content.isNotBlank() -> {
|
||||
updateLessonView(item, emptyList(), binding, currentDayHeader)
|
||||
binding.dashboardLessonsItemTitleTomorrow.isVisible = false
|
||||
}
|
||||
tomorrowDayHeader != null && tomorrowDayHeader.content.isNotBlank() -> {
|
||||
updateLessonView(item, emptyList(), binding, tomorrowDayHeader)
|
||||
binding.dashboardLessonsItemTitleTomorrow.isVisible = true
|
||||
@ -348,6 +352,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun updateFirstLessonView(
|
||||
binding: ItemDashboardLessonsBinding,
|
||||
firstLesson: Timetable?,
|
||||
@ -367,7 +372,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
firstLesson ?: return
|
||||
|
||||
val minutesToStartLesson =
|
||||
Duration.between(currentDateTime, firstLesson.start).toMinutes()
|
||||
Duration.between(currentDateTime, firstLesson.start).toMinutes() + 1
|
||||
val isFirstTimeVisible: Boolean
|
||||
val isFirstTimeRangeVisible: Boolean
|
||||
val firstTimeText: String
|
||||
@ -376,12 +381,12 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
val firstTitleAndValueTextColor: Int
|
||||
val firstTitleAndValueTextFont: Typeface
|
||||
|
||||
if (currentDateTime.isBefore(firstLesson.start)) {
|
||||
if (currentDateTime < firstLesson.start) {
|
||||
if (minutesToStartLesson > 60) {
|
||||
val formattedStartTime = firstLesson.start.toFormattedString("HH:mm")
|
||||
val formattedEndTime = firstLesson.end.toFormattedString("HH:mm")
|
||||
|
||||
firstTimeRangeText = "${formattedStartTime}-${formattedEndTime}"
|
||||
firstTimeRangeText = "$formattedStartTime - $formattedEndTime"
|
||||
firstTimeText = ""
|
||||
|
||||
isFirstTimeRangeVisible = true
|
||||
@ -421,7 +426,7 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val minutesToEndLesson = firstLesson.left!!.toMinutes()
|
||||
val minutesToEndLesson = firstLesson.left!!.toMinutes() + 1
|
||||
|
||||
firstTimeText = context.resources.getQuantityString(
|
||||
R.plurals.dashboard_timetable_first_lesson_time_more_minutes,
|
||||
@ -454,11 +459,8 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
with(binding.dashboardLessonsItemFirstValue) {
|
||||
setTextColor(firstTitleAndValueTextColor)
|
||||
typeface = firstTitleAndValueTextFont
|
||||
text = context.getString(
|
||||
R.string.dashboard_timetable_lesson_value,
|
||||
firstLesson.subject,
|
||||
firstLesson.room
|
||||
)
|
||||
text =
|
||||
"${firstLesson.subject} ${if (firstLesson.room.isNotBlank()) "(${firstLesson.room})" else ""}"
|
||||
}
|
||||
}
|
||||
|
||||
@ -472,13 +474,11 @@ class DashboardAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView
|
||||
val formattedStartTime = secondLesson?.start?.toFormattedString("HH:mm")
|
||||
val formattedEndTime = secondLesson?.end?.toFormattedString("HH:mm")
|
||||
|
||||
val secondTimeText = "${formattedStartTime}-${formattedEndTime}"
|
||||
val secondTimeText = "$formattedStartTime - $formattedEndTime"
|
||||
val secondValueText = if (secondLesson != null) {
|
||||
context.getString(
|
||||
R.string.dashboard_timetable_lesson_value,
|
||||
secondLesson.subject,
|
||||
secondLesson.room
|
||||
)
|
||||
val roomString = if (secondLesson.room.isNotBlank()) "(${secondLesson.room})" else ""
|
||||
|
||||
"${secondLesson.subject} $roomString"
|
||||
} else {
|
||||
context.getString(R.string.dashboard_timetable_second_lesson_value_end)
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.databinding.FragmentDashboardBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.account.AccountFragment
|
||||
import io.github.wulkanowy.ui.modules.account.accountdetails.AccountDetailsFragment
|
||||
import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment
|
||||
import io.github.wulkanowy.ui.modules.conference.ConferenceFragment
|
||||
import io.github.wulkanowy.ui.modules.exam.ExamFragment
|
||||
@ -77,7 +77,9 @@ class DashboardFragment : BaseFragment<FragmentDashboardBinding>(R.layout.fragme
|
||||
)
|
||||
|
||||
dashboardAdapter.apply {
|
||||
onAccountTileClickListener = { mainActivity.pushView(AccountFragment.newInstance()) }
|
||||
onAccountTileClickListener = {
|
||||
mainActivity.pushView(AccountDetailsFragment.newInstance(it))
|
||||
}
|
||||
onLuckyNumberTileClickListener = {
|
||||
mainActivity.pushView(LuckyNumberFragment.newInstance())
|
||||
}
|
||||
|
@ -35,6 +35,9 @@ sealed class DashboardItem(val type: Type) {
|
||||
|
||||
override val isDataLoaded
|
||||
get() = unreadMessagesCount != null || attendancePercentage != null || luckyNumber != null
|
||||
|
||||
val isFullDataLoaded
|
||||
get() = luckyNumber != -1 && attendancePercentage != -1.0 && unreadMessagesCount != -1
|
||||
}
|
||||
|
||||
data class Grades(
|
||||
|
@ -2,6 +2,8 @@ package io.github.wulkanowy.ui.modules.dashboard
|
||||
|
||||
import io.github.wulkanowy.data.Resource
|
||||
import io.github.wulkanowy.data.Status
|
||||
import io.github.wulkanowy.data.db.entities.LuckyNumber
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.enums.MessageFolder
|
||||
import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository
|
||||
import io.github.wulkanowy.data.repositories.ConferenceRepository
|
||||
@ -18,11 +20,18 @@ import io.github.wulkanowy.data.repositories.TimetableRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.calculatePercentage
|
||||
import io.github.wulkanowy.utils.flowWithResource
|
||||
import io.github.wulkanowy.utils.flowWithResourceIn
|
||||
import io.github.wulkanowy.utils.nextOrSameSchoolDay
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.filterNot
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
@ -48,9 +57,11 @@ class DashboardPresenter @Inject constructor(
|
||||
|
||||
private val dashboardItemRefreshLoadedList = mutableListOf<DashboardItem>()
|
||||
|
||||
private lateinit var dashboardItemsToLoad: Set<DashboardItem.Type>
|
||||
private var dashboardItemsToLoad = emptySet<DashboardItem.Type>()
|
||||
|
||||
private var dashboardTilesToLoad: Set<DashboardItem.Tile> = emptySet()
|
||||
private var dashboardTileLoadedList = emptySet<DashboardItem.Tile>()
|
||||
|
||||
private val firstLoadedItemList = mutableListOf<DashboardItem.Type>()
|
||||
|
||||
private lateinit var lastError: Throwable
|
||||
|
||||
@ -69,8 +80,10 @@ class DashboardPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
fun onDragAndDropEnd(list: List<DashboardItem>) {
|
||||
dashboardItemLoadedList.clear()
|
||||
dashboardItemLoadedList.addAll(list)
|
||||
with(dashboardItemLoadedList) {
|
||||
clear()
|
||||
addAll(list)
|
||||
}
|
||||
|
||||
val positionList =
|
||||
list.mapIndexed { index, dashboardItem -> Pair(dashboardItem.type, index) }.toMap()
|
||||
@ -78,87 +91,102 @@ class DashboardPresenter @Inject constructor(
|
||||
preferencesRepository.dashboardItemsPosition = positionList
|
||||
}
|
||||
|
||||
fun loadData(forceRefresh: Boolean = false, tilesToLoad: Set<DashboardItem.Tile>) {
|
||||
val oldDashboardDataToLoad = dashboardTilesToLoad
|
||||
fun loadData(
|
||||
tilesToLoad: Set<DashboardItem.Tile>,
|
||||
forceRefresh: Boolean = false,
|
||||
) {
|
||||
val oldDashboardTileLoadedList = dashboardTileLoadedList
|
||||
dashboardItemsToLoad = tilesToLoad.map { it.toDashboardItemType() }.toSet()
|
||||
dashboardTileLoadedList = tilesToLoad
|
||||
|
||||
dashboardTilesToLoad = tilesToLoad
|
||||
dashboardItemsToLoad = dashboardTilesToLoad.map { it.toDashboardItemType() }.toSet()
|
||||
val itemsToLoad = generateDashboardTileListToLoad(
|
||||
dashboardTilesToLoad = tilesToLoad,
|
||||
dashboardLoadedTiles = oldDashboardTileLoadedList,
|
||||
forceRefresh = forceRefresh
|
||||
).map { it.toDashboardItemType() }
|
||||
|
||||
removeUnselectedTiles()
|
||||
|
||||
val newTileList = generateTileListToLoad(oldDashboardDataToLoad, forceRefresh)
|
||||
loadTiles(forceRefresh, newTileList)
|
||||
removeUnselectedTiles(tilesToLoad.toList())
|
||||
loadTiles(tileList = itemsToLoad, forceRefresh = forceRefresh)
|
||||
}
|
||||
|
||||
private fun removeUnselectedTiles() {
|
||||
val isLuckyNumberToLoad =
|
||||
dashboardTilesToLoad.any { it == DashboardItem.Tile.LUCKY_NUMBER }
|
||||
val isMessagesToLoad =
|
||||
dashboardTilesToLoad.any { it == DashboardItem.Tile.MESSAGES }
|
||||
val isAttendanceToLoad =
|
||||
dashboardTilesToLoad.any { it == DashboardItem.Tile.ATTENDANCE }
|
||||
private fun generateDashboardTileListToLoad(
|
||||
dashboardTilesToLoad: Set<DashboardItem.Tile>,
|
||||
dashboardLoadedTiles: Set<DashboardItem.Tile>,
|
||||
forceRefresh: Boolean
|
||||
) = dashboardTilesToLoad.filter { newItemToLoad ->
|
||||
dashboardLoadedTiles.none { it == newItemToLoad } || forceRefresh
|
||||
}
|
||||
|
||||
private fun removeUnselectedTiles(tilesToLoad: List<DashboardItem.Tile>) {
|
||||
dashboardItemLoadedList.removeAll { loadedTile -> dashboardItemsToLoad.none { it == loadedTile.type } }
|
||||
|
||||
val horizontalGroup =
|
||||
dashboardItemLoadedList.find { it is DashboardItem.HorizontalGroup } as DashboardItem.HorizontalGroup?
|
||||
|
||||
if (horizontalGroup != null) {
|
||||
val horizontalIndex = dashboardItemLoadedList.indexOf(horizontalGroup)
|
||||
dashboardItemLoadedList.remove(horizontalGroup)
|
||||
val isLuckyNumberToLoad = DashboardItem.Tile.LUCKY_NUMBER in tilesToLoad
|
||||
val isMessagesToLoad = DashboardItem.Tile.MESSAGES in tilesToLoad
|
||||
val isAttendanceToLoad = DashboardItem.Tile.ATTENDANCE in tilesToLoad
|
||||
|
||||
var updatedHorizontalGroup = horizontalGroup
|
||||
val horizontalGroupIndex = dashboardItemLoadedList.indexOf(horizontalGroup)
|
||||
|
||||
if (horizontalGroup.luckyNumber != null && !isLuckyNumberToLoad) {
|
||||
updatedHorizontalGroup = updatedHorizontalGroup.copy(luckyNumber = null)
|
||||
val newHorizontalGroup = horizontalGroup.copy(
|
||||
attendancePercentage = horizontalGroup.attendancePercentage.takeIf { isAttendanceToLoad },
|
||||
unreadMessagesCount = horizontalGroup.unreadMessagesCount.takeIf { isMessagesToLoad },
|
||||
luckyNumber = horizontalGroup.luckyNumber.takeIf { isLuckyNumberToLoad }
|
||||
)
|
||||
|
||||
with(dashboardItemLoadedList) {
|
||||
removeAt(horizontalGroupIndex)
|
||||
add(horizontalGroupIndex, newHorizontalGroup)
|
||||
}
|
||||
|
||||
if (horizontalGroup.attendancePercentage != null && !isAttendanceToLoad) {
|
||||
updatedHorizontalGroup = updatedHorizontalGroup.copy(attendancePercentage = null)
|
||||
}
|
||||
|
||||
if (horizontalGroup.unreadMessagesCount != null && !isMessagesToLoad) {
|
||||
updatedHorizontalGroup = updatedHorizontalGroup.copy(unreadMessagesCount = null)
|
||||
}
|
||||
|
||||
if (horizontalGroup.error != null) {
|
||||
updatedHorizontalGroup = updatedHorizontalGroup.copy(error = null, isLoading = true)
|
||||
}
|
||||
|
||||
dashboardItemLoadedList.add(horizontalIndex, updatedHorizontalGroup)
|
||||
}
|
||||
|
||||
view?.updateData(dashboardItemLoadedList)
|
||||
}
|
||||
|
||||
private fun loadTiles(forceRefresh: Boolean, tileList: List<DashboardItem.Tile>) {
|
||||
tileList.forEach {
|
||||
when (it) {
|
||||
DashboardItem.Tile.ACCOUNT -> loadCurrentAccount(forceRefresh)
|
||||
DashboardItem.Tile.LUCKY_NUMBER -> loadLuckyNumber(forceRefresh)
|
||||
DashboardItem.Tile.MESSAGES -> loadMessages(forceRefresh)
|
||||
DashboardItem.Tile.ATTENDANCE -> loadAttendance(forceRefresh)
|
||||
DashboardItem.Tile.LESSONS -> loadLessons(forceRefresh)
|
||||
DashboardItem.Tile.GRADES -> loadGrades(forceRefresh)
|
||||
DashboardItem.Tile.HOMEWORK -> loadHomework(forceRefresh)
|
||||
DashboardItem.Tile.ANNOUNCEMENTS -> loadSchoolAnnouncements(forceRefresh)
|
||||
DashboardItem.Tile.EXAMS -> loadExams(forceRefresh)
|
||||
DashboardItem.Tile.CONFERENCES -> loadConferences(forceRefresh)
|
||||
DashboardItem.Tile.ADS -> TODO()
|
||||
private fun loadTiles(
|
||||
tileList: List<DashboardItem.Type>,
|
||||
forceRefresh: Boolean
|
||||
) {
|
||||
launch {
|
||||
Timber.i("Loading dashboard account data started")
|
||||
val student = runCatching { studentRepository.getCurrentStudent(true) }
|
||||
.onFailure {
|
||||
Timber.i("Loading dashboard account result: An exception occurred")
|
||||
errorHandler.dispatch(it)
|
||||
updateData(DashboardItem.Account(error = it), forceRefresh)
|
||||
}
|
||||
.onSuccess { Timber.i("Loading dashboard account result: Success") }
|
||||
.getOrNull() ?: return@launch
|
||||
|
||||
tileList.forEach {
|
||||
when (it) {
|
||||
DashboardItem.Type.ACCOUNT -> {
|
||||
updateData(DashboardItem.Account(student), forceRefresh)
|
||||
}
|
||||
DashboardItem.Type.HORIZONTAL_GROUP -> {
|
||||
loadHorizontalGroup(student, forceRefresh)
|
||||
}
|
||||
DashboardItem.Type.LESSONS -> loadLessons(student, forceRefresh)
|
||||
DashboardItem.Type.GRADES -> loadGrades(student, forceRefresh)
|
||||
DashboardItem.Type.HOMEWORK -> loadHomework(student, forceRefresh)
|
||||
DashboardItem.Type.ANNOUNCEMENTS -> {
|
||||
loadSchoolAnnouncements(student, forceRefresh)
|
||||
}
|
||||
DashboardItem.Type.EXAMS -> loadExams(student, forceRefresh)
|
||||
DashboardItem.Type.CONFERENCES -> {
|
||||
loadConferences(student, forceRefresh)
|
||||
}
|
||||
DashboardItem.Type.ADS -> TODO()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateTileListToLoad(
|
||||
oldDashboardTileToLoad: Set<DashboardItem.Tile>,
|
||||
forceRefresh: Boolean
|
||||
) = dashboardTilesToLoad.filter { newTileToLoad ->
|
||||
oldDashboardTileToLoad.none { it == newTileToLoad } || forceRefresh
|
||||
}
|
||||
|
||||
fun onSwipeRefresh() {
|
||||
Timber.i("Force refreshing the dashboard")
|
||||
loadData(true, preferencesRepository.selectedDashboardTiles)
|
||||
loadData(preferencesRepository.selectedDashboardTiles, forceRefresh = true)
|
||||
}
|
||||
|
||||
fun onRetry() {
|
||||
@ -166,7 +194,7 @@ class DashboardPresenter @Inject constructor(
|
||||
showErrorView(false)
|
||||
showProgress(true)
|
||||
}
|
||||
loadData(true, preferencesRepository.selectedDashboardTiles)
|
||||
loadData(preferencesRepository.selectedDashboardTiles, forceRefresh = true)
|
||||
}
|
||||
|
||||
fun onViewReselected() {
|
||||
@ -192,139 +220,86 @@ class DashboardPresenter @Inject constructor(
|
||||
}.toSet()
|
||||
}
|
||||
|
||||
private fun loadCurrentAccount(forceRefresh: Boolean) {
|
||||
flowWithResource { studentRepository.getCurrentStudent(false) }
|
||||
private fun loadHorizontalGroup(student: Student, forceRefresh: Boolean) {
|
||||
flow {
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
val selectedTiles = preferencesRepository.selectedDashboardTiles
|
||||
|
||||
val luckyNumberFlow = luckyNumberRepository.getLuckyNumber(student, forceRefresh)
|
||||
.map {
|
||||
if (it.data == null) {
|
||||
it.copy(data = LuckyNumber(0, LocalDate.now(), 0))
|
||||
} else it
|
||||
}
|
||||
.takeIf { DashboardItem.Tile.LUCKY_NUMBER in selectedTiles } ?: flowOf(null)
|
||||
|
||||
val messageFLow = messageRepository.getMessages(
|
||||
student = student,
|
||||
semester = semester,
|
||||
folder = MessageFolder.RECEIVED,
|
||||
forceRefresh = forceRefresh
|
||||
).takeIf { DashboardItem.Tile.MESSAGES in selectedTiles } ?: flowOf(null)
|
||||
|
||||
val attendanceFlow = attendanceSummaryRepository.getAttendanceSummary(
|
||||
student = student,
|
||||
semester = semester,
|
||||
subjectId = -1,
|
||||
forceRefresh = forceRefresh
|
||||
).takeIf { DashboardItem.Tile.ATTENDANCE in selectedTiles } ?: flowOf(null)
|
||||
|
||||
emitAll(
|
||||
combine(
|
||||
luckyNumberFlow,
|
||||
messageFLow,
|
||||
attendanceFlow
|
||||
) { luckyNumberResource, messageResource, attendanceResource ->
|
||||
val error =
|
||||
luckyNumberResource?.error ?: messageResource?.error ?: attendanceResource?.error
|
||||
error?.let { throw it }
|
||||
|
||||
val luckyNumber = luckyNumberResource?.data?.luckyNumber
|
||||
val messageCount = messageResource?.data?.count { it.unread }
|
||||
val attendancePercentage = attendanceResource?.data?.calculatePercentage()
|
||||
|
||||
val isLoading =
|
||||
luckyNumberResource?.status == Status.LOADING || messageResource?.status == Status.LOADING || attendanceResource?.status == Status.LOADING
|
||||
|
||||
DashboardItem.HorizontalGroup(
|
||||
isLoading = isLoading,
|
||||
attendancePercentage = if (attendancePercentage == 0.0 && isLoading) -1.0 else attendancePercentage,
|
||||
unreadMessagesCount = if (messageCount == 0 && isLoading) -1 else messageCount,
|
||||
luckyNumber = if (luckyNumber == 0 && isLoading) -1 else luckyNumber
|
||||
)
|
||||
})
|
||||
}
|
||||
.filterNot { it.isLoading && forceRefresh }
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> {
|
||||
Timber.i("Loading dashboard account data started")
|
||||
if (forceRefresh) return@onEach
|
||||
updateData(DashboardItem.Account(it.data, isLoading = true), forceRefresh)
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading dashboard account result: Success")
|
||||
updateData(DashboardItem.Account(it.data), forceRefresh)
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Loading dashboard account result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
updateData(DashboardItem.Account(error = it.error), forceRefresh)
|
||||
updateData(it, forceRefresh)
|
||||
|
||||
if (it.isLoading) {
|
||||
Timber.i("Loading horizontal group data started")
|
||||
|
||||
if (it.isFullDataLoaded) {
|
||||
firstLoadedItemList += DashboardItem.Type.HORIZONTAL_GROUP
|
||||
}
|
||||
} else {
|
||||
Timber.i("Loading horizontal group result: Success")
|
||||
}
|
||||
}
|
||||
.launch("dashboard_account")
|
||||
}
|
||||
|
||||
private fun loadLuckyNumber(forceRefresh: Boolean) {
|
||||
flowWithResourceIn {
|
||||
val student = studentRepository.getCurrentStudent(true)
|
||||
|
||||
luckyNumberRepository.getLuckyNumber(student, forceRefresh)
|
||||
}.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> {
|
||||
Timber.i("Loading dashboard lucky number data started")
|
||||
if (forceRefresh) return@onEach
|
||||
processHorizontalGroupData(
|
||||
luckyNumber = it.data?.luckyNumber,
|
||||
isLoading = true,
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading dashboard lucky number result: Success")
|
||||
processHorizontalGroupData(
|
||||
luckyNumber = it.data?.luckyNumber ?: -1,
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Loading dashboard lucky number result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
processHorizontalGroupData(error = it.error, forceRefresh = forceRefresh)
|
||||
}
|
||||
.catch {
|
||||
Timber.i("Loading horizontal group result: An exception occurred")
|
||||
updateData(
|
||||
DashboardItem.HorizontalGroup(error = it),
|
||||
forceRefresh,
|
||||
)
|
||||
errorHandler.dispatch(it)
|
||||
}
|
||||
}.launch("dashboard_lucky_number")
|
||||
.launch("horizontal_group")
|
||||
}
|
||||
|
||||
private fun loadMessages(forceRefresh: Boolean) {
|
||||
private fun loadGrades(student: Student, forceRefresh: Boolean) {
|
||||
flowWithResourceIn {
|
||||
val student = studentRepository.getCurrentStudent(true)
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
|
||||
messageRepository.getMessages(student, semester, MessageFolder.RECEIVED, forceRefresh)
|
||||
}.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> {
|
||||
Timber.i("Loading dashboard messages data started")
|
||||
if (forceRefresh) return@onEach
|
||||
val unreadMessagesCount = it.data?.count { message -> message.unread }
|
||||
|
||||
processHorizontalGroupData(
|
||||
unreadMessagesCount = unreadMessagesCount,
|
||||
isLoading = true,
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading dashboard messages result: Success")
|
||||
val unreadMessagesCount = it.data?.count { message -> message.unread }
|
||||
|
||||
processHorizontalGroupData(
|
||||
unreadMessagesCount = unreadMessagesCount,
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Loading dashboard messages result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
processHorizontalGroupData(error = it.error, forceRefresh = forceRefresh)
|
||||
}
|
||||
}
|
||||
}.launch("dashboard_messages")
|
||||
}
|
||||
|
||||
private fun loadAttendance(forceRefresh: Boolean) {
|
||||
flowWithResourceIn {
|
||||
val student = studentRepository.getCurrentStudent(true)
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
|
||||
attendanceSummaryRepository.getAttendanceSummary(student, semester, -1, forceRefresh)
|
||||
}.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> {
|
||||
Timber.i("Loading dashboard attendance data started")
|
||||
if (forceRefresh) return@onEach
|
||||
val attendancePercentage = it.data?.calculatePercentage()
|
||||
|
||||
processHorizontalGroupData(
|
||||
attendancePercentage = attendancePercentage,
|
||||
isLoading = true,
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading dashboard attendance result: Success")
|
||||
val attendancePercentage = it.data?.calculatePercentage()
|
||||
|
||||
processHorizontalGroupData(
|
||||
attendancePercentage = attendancePercentage,
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Loading dashboard attendance result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
|
||||
processHorizontalGroupData(error = it.error, forceRefresh = forceRefresh)
|
||||
}
|
||||
}
|
||||
}.launch("dashboard_attendance")
|
||||
}
|
||||
|
||||
private fun loadGrades(forceRefresh: Boolean) {
|
||||
flowWithResourceIn {
|
||||
val student = studentRepository.getCurrentStudent(true)
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
|
||||
gradeRepository.getGrades(student, semester, forceRefresh)
|
||||
@ -353,6 +328,7 @@ class DashboardPresenter @Inject constructor(
|
||||
Status.LOADING -> {
|
||||
Timber.i("Loading dashboard grades data started")
|
||||
if (forceRefresh) return@onEach
|
||||
|
||||
updateData(
|
||||
DashboardItem.Grades(
|
||||
subjectWithGrades = it.data,
|
||||
@ -360,6 +336,10 @@ class DashboardPresenter @Inject constructor(
|
||||
isLoading = true
|
||||
), forceRefresh
|
||||
)
|
||||
|
||||
if (!it.data.isNullOrEmpty()) {
|
||||
firstLoadedItemList += DashboardItem.Type.GRADES
|
||||
}
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading dashboard grades result: Success")
|
||||
@ -367,7 +347,8 @@ class DashboardPresenter @Inject constructor(
|
||||
DashboardItem.Grades(
|
||||
subjectWithGrades = it.data,
|
||||
gradeTheme = preferencesRepository.gradeColorTheme
|
||||
), forceRefresh
|
||||
),
|
||||
forceRefresh
|
||||
)
|
||||
}
|
||||
Status.ERROR -> {
|
||||
@ -379,9 +360,8 @@ class DashboardPresenter @Inject constructor(
|
||||
}.launch("dashboard_grades")
|
||||
}
|
||||
|
||||
private fun loadLessons(forceRefresh: Boolean) {
|
||||
private fun loadLessons(student: Student, forceRefresh: Boolean) {
|
||||
flowWithResourceIn {
|
||||
val student = studentRepository.getCurrentStudent(true)
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
val date = LocalDate.now().nextOrSameSchoolDay
|
||||
|
||||
@ -398,24 +378,34 @@ class DashboardPresenter @Inject constructor(
|
||||
Status.LOADING -> {
|
||||
Timber.i("Loading dashboard lessons data started")
|
||||
if (forceRefresh) return@onEach
|
||||
updateData(DashboardItem.Lessons(it.data, isLoading = true), forceRefresh)
|
||||
updateData(
|
||||
DashboardItem.Lessons(it.data, isLoading = true),
|
||||
forceRefresh
|
||||
)
|
||||
|
||||
if (!it.data?.lessons.isNullOrEmpty()) {
|
||||
firstLoadedItemList += DashboardItem.Type.LESSONS
|
||||
}
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading dashboard lessons result: Success")
|
||||
updateData(DashboardItem.Lessons(it.data), forceRefresh)
|
||||
updateData(
|
||||
DashboardItem.Lessons(it.data), forceRefresh
|
||||
)
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Loading dashboard lessons result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
updateData(DashboardItem.Lessons(error = it.error), forceRefresh)
|
||||
updateData(
|
||||
DashboardItem.Lessons(error = it.error), forceRefresh
|
||||
)
|
||||
}
|
||||
}
|
||||
}.launch("dashboard_lessons")
|
||||
}
|
||||
|
||||
private fun loadHomework(forceRefresh: Boolean) {
|
||||
private fun loadHomework(student: Student, forceRefresh: Boolean) {
|
||||
flowWithResourceIn {
|
||||
val student = studentRepository.getCurrentStudent(true)
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
val date = LocalDate.now().nextOrSameSchoolDay
|
||||
|
||||
@ -443,6 +433,10 @@ class DashboardPresenter @Inject constructor(
|
||||
DashboardItem.Homework(it.data ?: emptyList(), isLoading = true),
|
||||
forceRefresh
|
||||
)
|
||||
|
||||
if (!it.data.isNullOrEmpty()) {
|
||||
firstLoadedItemList += DashboardItem.Type.HOMEWORK
|
||||
}
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading dashboard homework result: Success")
|
||||
@ -457,10 +451,8 @@ class DashboardPresenter @Inject constructor(
|
||||
}.launch("dashboard_homework")
|
||||
}
|
||||
|
||||
private fun loadSchoolAnnouncements(forceRefresh: Boolean) {
|
||||
private fun loadSchoolAnnouncements(student: Student, forceRefresh: Boolean) {
|
||||
flowWithResourceIn {
|
||||
val student = studentRepository.getCurrentStudent(true)
|
||||
|
||||
schoolAnnouncementRepository.getSchoolAnnouncements(student, forceRefresh)
|
||||
}.onEach {
|
||||
when (it.status) {
|
||||
@ -468,11 +460,13 @@ class DashboardPresenter @Inject constructor(
|
||||
Timber.i("Loading dashboard announcements data started")
|
||||
if (forceRefresh) return@onEach
|
||||
updateData(
|
||||
DashboardItem.Announcements(
|
||||
it.data ?: emptyList(),
|
||||
isLoading = true
|
||||
), forceRefresh
|
||||
DashboardItem.Announcements(it.data ?: emptyList(), isLoading = true),
|
||||
forceRefresh
|
||||
)
|
||||
|
||||
if (!it.data.isNullOrEmpty()) {
|
||||
firstLoadedItemList += DashboardItem.Type.ANNOUNCEMENTS
|
||||
}
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading dashboard announcements result: Success")
|
||||
@ -487,9 +481,8 @@ class DashboardPresenter @Inject constructor(
|
||||
}.launch("dashboard_announcements")
|
||||
}
|
||||
|
||||
private fun loadExams(forceRefresh: Boolean) {
|
||||
private fun loadExams(student: Student, forceRefresh: Boolean) {
|
||||
flowWithResourceIn {
|
||||
val student = studentRepository.getCurrentStudent(true)
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
|
||||
examRepository.getExams(
|
||||
@ -508,6 +501,10 @@ class DashboardPresenter @Inject constructor(
|
||||
DashboardItem.Exams(it.data.orEmpty(), isLoading = true),
|
||||
forceRefresh
|
||||
)
|
||||
|
||||
if (!it.data.isNullOrEmpty()) {
|
||||
firstLoadedItemList += DashboardItem.Type.EXAMS
|
||||
}
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading dashboard exams result: Success")
|
||||
@ -522,9 +519,8 @@ class DashboardPresenter @Inject constructor(
|
||||
}.launch("dashboard_exams")
|
||||
}
|
||||
|
||||
private fun loadConferences(forceRefresh: Boolean) {
|
||||
private fun loadConferences(student: Student, forceRefresh: Boolean) {
|
||||
flowWithResourceIn {
|
||||
val student = studentRepository.getCurrentStudent(true)
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
|
||||
conferenceRepository.getConferences(
|
||||
@ -542,6 +538,10 @@ class DashboardPresenter @Inject constructor(
|
||||
DashboardItem.Conferences(it.data ?: emptyList(), isLoading = true),
|
||||
forceRefresh
|
||||
)
|
||||
|
||||
if (!it.data.isNullOrEmpty()) {
|
||||
firstLoadedItemList += DashboardItem.Type.CONFERENCES
|
||||
}
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading dashboard conferences result: Success")
|
||||
@ -556,145 +556,119 @@ class DashboardPresenter @Inject constructor(
|
||||
}.launch("dashboard_conferences")
|
||||
}
|
||||
|
||||
private fun processHorizontalGroupData(
|
||||
luckyNumber: Int? = null,
|
||||
unreadMessagesCount: Int? = null,
|
||||
attendancePercentage: Double? = null,
|
||||
error: Throwable? = null,
|
||||
isLoading: Boolean = false,
|
||||
forceRefresh: Boolean
|
||||
) {
|
||||
val isLuckyNumberToLoad =
|
||||
dashboardTilesToLoad.any { it == DashboardItem.Tile.LUCKY_NUMBER }
|
||||
val isMessagesToLoad =
|
||||
dashboardTilesToLoad.any { it == DashboardItem.Tile.MESSAGES }
|
||||
val isAttendanceToLoad =
|
||||
dashboardTilesToLoad.any { it == DashboardItem.Tile.ATTENDANCE }
|
||||
val isPushedToList =
|
||||
dashboardItemLoadedList.any { it.type == DashboardItem.Type.HORIZONTAL_GROUP }
|
||||
private fun updateData(dashboardItem: DashboardItem, forceRefresh: Boolean) {
|
||||
val isForceRefreshError = forceRefresh && dashboardItem.error != null
|
||||
val isFirstRunDataLoadedError =
|
||||
dashboardItem.type in firstLoadedItemList && dashboardItem.error != null
|
||||
|
||||
if (error != null) {
|
||||
updateData(DashboardItem.HorizontalGroup(error = error), forceRefresh)
|
||||
return
|
||||
with(dashboardItemLoadedList) {
|
||||
removeAll { it.type == dashboardItem.type && !isForceRefreshError && !isFirstRunDataLoadedError }
|
||||
if (!isForceRefreshError && !isFirstRunDataLoadedError) add(dashboardItem)
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
val horizontalGroup =
|
||||
dashboardItemLoadedList.find { it is DashboardItem.HorizontalGroup } as DashboardItem.HorizontalGroup?
|
||||
val updatedHorizontalGroup =
|
||||
horizontalGroup?.copy(isLoading = true) ?: DashboardItem.HorizontalGroup(isLoading = true)
|
||||
sortDashboardItems()
|
||||
|
||||
updateData(updatedHorizontalGroup, forceRefresh)
|
||||
}
|
||||
|
||||
if (forceRefresh && !isPushedToList) {
|
||||
updateData(DashboardItem.HorizontalGroup(), forceRefresh)
|
||||
}
|
||||
|
||||
val horizontalGroup =
|
||||
dashboardItemLoadedList.single { it is DashboardItem.HorizontalGroup } as DashboardItem.HorizontalGroup
|
||||
|
||||
when {
|
||||
luckyNumber != null -> {
|
||||
updateData(horizontalGroup.copy(luckyNumber = luckyNumber), forceRefresh)
|
||||
}
|
||||
unreadMessagesCount != null -> {
|
||||
updateData(
|
||||
horizontalGroup.copy(unreadMessagesCount = unreadMessagesCount),
|
||||
forceRefresh
|
||||
)
|
||||
}
|
||||
attendancePercentage != null -> {
|
||||
updateData(
|
||||
horizontalGroup.copy(attendancePercentage = attendancePercentage),
|
||||
forceRefresh
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val isHorizontalGroupLoaded = dashboardItemLoadedList.any {
|
||||
if (it !is DashboardItem.HorizontalGroup) return@any false
|
||||
|
||||
val isLuckyNumberStateCorrect = (it.luckyNumber != null) == isLuckyNumberToLoad
|
||||
val isMessagesStateCorrect = (it.unreadMessagesCount != null) == isMessagesToLoad
|
||||
val isAttendanceStateCorrect = (it.attendancePercentage != null) == isAttendanceToLoad
|
||||
|
||||
isLuckyNumberStateCorrect && isAttendanceStateCorrect && isMessagesStateCorrect
|
||||
}
|
||||
|
||||
if (isHorizontalGroupLoaded) {
|
||||
val updatedHorizontalGroup =
|
||||
dashboardItemLoadedList.single { it is DashboardItem.HorizontalGroup } as DashboardItem.HorizontalGroup
|
||||
|
||||
updateData(updatedHorizontalGroup.copy(isLoading = false, error = null), forceRefresh)
|
||||
if (forceRefresh) {
|
||||
updateForceRefreshData(dashboardItem)
|
||||
} else {
|
||||
updateNormalData()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateData(dashboardItem: DashboardItem, forceRefresh: Boolean) {
|
||||
val isForceRefreshError = forceRefresh && dashboardItem.error != null
|
||||
val dashboardItemsPosition = preferencesRepository.dashboardItemsPosition
|
||||
|
||||
with(dashboardItemLoadedList) {
|
||||
removeAll { it.type == dashboardItem.type && !isForceRefreshError }
|
||||
if (!isForceRefreshError) add(dashboardItem)
|
||||
sortBy { tile -> dashboardItemsToLoad.single { it == tile.type }.ordinal }
|
||||
private fun updateNormalData() {
|
||||
val isItemsLoaded =
|
||||
dashboardItemsToLoad.all { type -> dashboardItemLoadedList.any { it.type == type } }
|
||||
val isItemsDataLoaded = isItemsLoaded && dashboardItemLoadedList.all {
|
||||
it.isDataLoaded || it.error != null
|
||||
}
|
||||
|
||||
if (forceRefresh) {
|
||||
with(dashboardItemRefreshLoadedList) {
|
||||
removeAll { it.type == dashboardItem.type }
|
||||
add(dashboardItem)
|
||||
if (isItemsDataLoaded) {
|
||||
view?.run {
|
||||
showProgress(false)
|
||||
showErrorView(false)
|
||||
showContent(true)
|
||||
updateData(dashboardItemLoadedList.toList())
|
||||
}
|
||||
}
|
||||
|
||||
showErrorIfExists(
|
||||
isItemsLoaded = isItemsLoaded,
|
||||
itemsLoadedList = dashboardItemLoadedList,
|
||||
forceRefresh = false
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateForceRefreshData(dashboardItem: DashboardItem) {
|
||||
with(dashboardItemRefreshLoadedList) {
|
||||
removeAll { it.type == dashboardItem.type }
|
||||
add(dashboardItem)
|
||||
}
|
||||
|
||||
val isRefreshItemLoaded =
|
||||
dashboardItemsToLoad.all { type -> dashboardItemRefreshLoadedList.any { it.type == type } }
|
||||
val isRefreshItemsDataLoaded = isRefreshItemLoaded && dashboardItemRefreshLoadedList.all {
|
||||
it.isDataLoaded || it.error != null
|
||||
}
|
||||
|
||||
if (isRefreshItemsDataLoaded) {
|
||||
view?.run {
|
||||
showRefresh(false)
|
||||
showErrorView(false)
|
||||
showContent(true)
|
||||
updateData(dashboardItemLoadedList.toList())
|
||||
}
|
||||
}
|
||||
|
||||
showErrorIfExists(
|
||||
isItemsLoaded = isRefreshItemLoaded,
|
||||
itemsLoadedList = dashboardItemRefreshLoadedList,
|
||||
forceRefresh = true
|
||||
)
|
||||
|
||||
if (isRefreshItemsDataLoaded) dashboardItemRefreshLoadedList.clear()
|
||||
}
|
||||
|
||||
private fun showErrorIfExists(
|
||||
isItemsLoaded: Boolean,
|
||||
itemsLoadedList: List<DashboardItem>,
|
||||
forceRefresh: Boolean
|
||||
) {
|
||||
val filteredItems = itemsLoadedList.filterNot { it.type == DashboardItem.Type.ACCOUNT }
|
||||
val isAccountItemError =
|
||||
itemsLoadedList.find { it.type == DashboardItem.Type.ACCOUNT }?.error != null
|
||||
val isGeneralError =
|
||||
filteredItems.none { it.error == null } && filteredItems.isNotEmpty() || isAccountItemError
|
||||
val errorMessage = itemsLoadedList.map { it.error?.stackTraceToString() }.toString()
|
||||
|
||||
val filteredOriginalLoadedList =
|
||||
dashboardItemLoadedList.filterNot { it.type == DashboardItem.Type.ACCOUNT }
|
||||
val wasAccountItemError =
|
||||
dashboardItemLoadedList.find { it.type == DashboardItem.Type.ACCOUNT }?.error != null
|
||||
val wasGeneralError =
|
||||
filteredOriginalLoadedList.none { it.error == null } && filteredOriginalLoadedList.isNotEmpty() || wasAccountItemError
|
||||
|
||||
if (isGeneralError && isItemsLoaded) {
|
||||
lastError = Exception(errorMessage)
|
||||
|
||||
view?.run {
|
||||
showProgress(false)
|
||||
showRefresh(false)
|
||||
if ((forceRefresh && wasGeneralError) || !forceRefresh) {
|
||||
showContent(false)
|
||||
showErrorView(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun sortDashboardItems() {
|
||||
val dashboardItemsPosition = preferencesRepository.dashboardItemsPosition
|
||||
|
||||
dashboardItemLoadedList.sortBy { tile ->
|
||||
dashboardItemsPosition?.getOrDefault(
|
||||
tile.type,
|
||||
tile.type.ordinal + 100
|
||||
) ?: tile.type.ordinal
|
||||
}
|
||||
|
||||
val isItemsLoaded =
|
||||
dashboardItemsToLoad.all { type -> dashboardItemLoadedList.any { it.type == type } }
|
||||
val isRefreshItemLoaded =
|
||||
dashboardItemsToLoad.all { type -> dashboardItemRefreshLoadedList.any { it.type == type } }
|
||||
val isItemsDataLoaded = isItemsLoaded && dashboardItemLoadedList.all {
|
||||
it.isDataLoaded || it.error != null
|
||||
}
|
||||
val isRefreshItemsDataLoaded = isRefreshItemLoaded && dashboardItemRefreshLoadedList.all {
|
||||
it.isDataLoaded || it.error != null
|
||||
}
|
||||
|
||||
if (isRefreshItemsDataLoaded) {
|
||||
view?.showRefresh(false)
|
||||
dashboardItemRefreshLoadedList.clear()
|
||||
}
|
||||
|
||||
view?.run {
|
||||
if (!forceRefresh) {
|
||||
showProgress(!isItemsDataLoaded)
|
||||
showContent(isItemsDataLoaded)
|
||||
}
|
||||
updateData(dashboardItemLoadedList.toList())
|
||||
}
|
||||
|
||||
if (isItemsLoaded) {
|
||||
val filteredItems =
|
||||
dashboardItemLoadedList.filterNot { it.type == DashboardItem.Type.ACCOUNT }
|
||||
val isAccountItemError =
|
||||
dashboardItemLoadedList.single { it.type == DashboardItem.Type.ACCOUNT }.error != null
|
||||
val isGeneralError =
|
||||
filteredItems.all { it.error != null } && filteredItems.isNotEmpty() || isAccountItemError
|
||||
|
||||
val errorMessage = filteredItems.map { it.error?.stackTraceToString() }.toString()
|
||||
|
||||
lastError = Exception(errorMessage)
|
||||
|
||||
view?.run {
|
||||
showProgress(false)
|
||||
showContent(!isGeneralError)
|
||||
showErrorView(isGeneralError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ package io.github.wulkanowy.ui.modules.schoolannouncement
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.core.text.parseAsHtml
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||
import io.github.wulkanowy.databinding.ItemSchoolAnnouncementBinding
|
||||
@ -14,6 +14,8 @@ class SchoolAnnouncementAdapter @Inject constructor() :
|
||||
|
||||
var items = emptyList<SchoolAnnouncement>()
|
||||
|
||||
var onItemClickListener: (SchoolAnnouncement) -> Unit = {}
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||
@ -26,9 +28,9 @@ class SchoolAnnouncementAdapter @Inject constructor() :
|
||||
with(holder.binding) {
|
||||
schoolAnnouncementItemDate.text = item.date.toFormattedString()
|
||||
schoolAnnouncementItemType.text = item.subject
|
||||
schoolAnnouncementItemContent.text = HtmlCompat.fromHtml(
|
||||
item.content, HtmlCompat.FROM_HTML_MODE_COMPACT
|
||||
)
|
||||
schoolAnnouncementItemContent.text = item.content.parseAsHtml()
|
||||
|
||||
root.setOnClickListener { onItemClickListener(item) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,54 @@
|
||||
package io.github.wulkanowy.ui.modules.schoolannouncement
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.text.parseAsHtml
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||
import io.github.wulkanowy.databinding.DialogSchoolAnnouncementBinding
|
||||
import io.github.wulkanowy.utils.lifecycleAwareVariable
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
|
||||
class SchoolAnnouncementDialog : DialogFragment() {
|
||||
|
||||
private var binding: DialogSchoolAnnouncementBinding by lifecycleAwareVariable()
|
||||
|
||||
private lateinit var announcement: SchoolAnnouncement
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ARGUMENT_KEY = "item"
|
||||
|
||||
fun newInstance(exam: SchoolAnnouncement) = SchoolAnnouncementDialog().apply {
|
||||
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setStyle(STYLE_NO_TITLE, 0)
|
||||
arguments?.run {
|
||||
announcement = getSerializable(ARGUMENT_KEY) as SchoolAnnouncement
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
) = DialogSchoolAnnouncementBinding.inflate(inflater).apply { binding = this }.root
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
with(binding) {
|
||||
announcementDialogSubjectValue.text = announcement.subject
|
||||
announcementDialogDateValue.text = announcement.date.toFormattedString()
|
||||
announcementDialogDescriptionValue.text = announcement.content.parseAsHtml()
|
||||
|
||||
announcementDialogClose.setOnClickListener { dismiss() }
|
||||
}
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||
import io.github.wulkanowy.databinding.FragmentSchoolAnnouncementBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainActivity
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
||||
import io.github.wulkanowy.utils.getThemeAttrColor
|
||||
@ -43,7 +44,9 @@ class SchoolAnnouncementFragment :
|
||||
override fun initView() {
|
||||
with(binding.directorInformationRecycler) {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = schoolAnnouncementAdapter
|
||||
adapter = schoolAnnouncementAdapter.apply {
|
||||
onItemClickListener = presenter::onItemClickListener
|
||||
}
|
||||
addItemDecoration(DividerItemDecoration(context))
|
||||
}
|
||||
with(binding) {
|
||||
@ -99,6 +102,10 @@ class SchoolAnnouncementFragment :
|
||||
binding.directorInformationSwipe.isRefreshing = show
|
||||
}
|
||||
|
||||
override fun openSchoolAnnouncementDialog(item: SchoolAnnouncement) {
|
||||
(activity as? MainActivity)?.showDialogFragment(SchoolAnnouncementDialog.newInstance(item))
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
presenter.onDetachView()
|
||||
super.onDestroyView()
|
||||
|
@ -1,6 +1,7 @@
|
||||
package io.github.wulkanowy.ui.modules.schoolannouncement
|
||||
|
||||
import io.github.wulkanowy.data.Status
|
||||
import io.github.wulkanowy.data.db.entities.SchoolAnnouncement
|
||||
import io.github.wulkanowy.data.repositories.SchoolAnnouncementRepository
|
||||
import io.github.wulkanowy.data.repositories.StudentRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
@ -46,6 +47,10 @@ class SchoolAnnouncementPresenter @Inject constructor(
|
||||
view?.showErrorDetailsDialog(lastError)
|
||||
}
|
||||
|
||||
fun onItemClickListener(item: SchoolAnnouncement) {
|
||||
view?.openSchoolAnnouncementDialog(item)
|
||||
}
|
||||
|
||||
private fun loadData(forceRefresh: Boolean = false) {
|
||||
Timber.i("Loading School announcement data started")
|
||||
|
||||
|
@ -19,6 +19,8 @@ interface SchoolAnnouncementView : BaseView {
|
||||
|
||||
fun setErrorDetails(message: String)
|
||||
|
||||
fun openSchoolAnnouncementDialog(item: SchoolAnnouncement)
|
||||
|
||||
fun showProgress(show: Boolean)
|
||||
|
||||
fun enableSwipe(enable: Boolean)
|
||||
|
@ -13,7 +13,7 @@ class StudentInfoAdapter @Inject constructor() :
|
||||
|
||||
var items = listOf<StudentInfoItem>()
|
||||
|
||||
var onItemClickListener: (position: Int) -> Unit = {}
|
||||
var onItemClickListener: (StudentInfoView.Type?) -> Unit = {}
|
||||
|
||||
var onItemLongClickListener: (text: String) -> Unit = {}
|
||||
|
||||
@ -32,7 +32,7 @@ class StudentInfoAdapter @Inject constructor() :
|
||||
studentInfoItemArrow.visibility = if (item.showArrow) VISIBLE else GONE
|
||||
|
||||
with(root) {
|
||||
setOnClickListener { onItemClickListener(position) }
|
||||
setOnClickListener { onItemClickListener(item.viewType) }
|
||||
setOnLongClickListener {
|
||||
onItemLongClickListener(studentInfoItemSubtitle.text.toString())
|
||||
true
|
||||
|
@ -1,6 +1,5 @@
|
||||
package io.github.wulkanowy.ui.modules.studentinfo
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.os.Bundle
|
||||
@ -130,9 +129,9 @@ class StudentInfoFragment :
|
||||
getString(R.string.student_info_parents_name) to studentInfo.parentsNames
|
||||
).map {
|
||||
StudentInfoItem(
|
||||
it.first,
|
||||
it.second.ifBlank { getString(R.string.all_no_data) },
|
||||
false,
|
||||
title = it.first,
|
||||
subtitle = it.second.ifBlank { getString(R.string.all_no_data) },
|
||||
showArrow = false,
|
||||
)
|
||||
}
|
||||
)
|
||||
@ -146,25 +145,33 @@ class StudentInfoFragment :
|
||||
getString(R.string.student_info_email) to studentInfo.email
|
||||
).map {
|
||||
StudentInfoItem(
|
||||
it.first,
|
||||
it.second.ifBlank { getString(R.string.all_no_data) },
|
||||
false,
|
||||
title = it.first,
|
||||
subtitle = it.second.ifBlank { getString(R.string.all_no_data) },
|
||||
showArrow = false,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
override fun showFamilyTypeData(studentInfo: StudentInfo) {
|
||||
val items = buildList {
|
||||
add(studentInfo.firstGuardian?.let {
|
||||
Triple(it.kinship.capitalise(), it.fullName, StudentInfoView.Type.FIRST_GUARDIAN)
|
||||
})
|
||||
|
||||
add(studentInfo.secondGuardian?.let {
|
||||
Triple(it.kinship.capitalise(), it.fullName, StudentInfoView.Type.SECOND_GUARDIAN)
|
||||
})
|
||||
}.filterNotNull()
|
||||
|
||||
updateData(
|
||||
listOfNotNull(
|
||||
studentInfo.firstGuardian?.let { it.kinship.capitalise() to it.fullName },
|
||||
studentInfo.secondGuardian?.let { it.kinship.capitalise() to it.fullName },
|
||||
).map { (title, value) ->
|
||||
items.map { (title, value, type) ->
|
||||
StudentInfoItem(
|
||||
title.ifBlank { getString(R.string.all_no_data) },
|
||||
value.ifBlank { getString(R.string.all_no_data) },
|
||||
true,
|
||||
title = title.ifBlank { getString(R.string.all_no_data) },
|
||||
subtitle = value.ifBlank { getString(R.string.all_no_data) },
|
||||
showArrow = true,
|
||||
viewType = type,
|
||||
)
|
||||
}
|
||||
)
|
||||
@ -178,15 +185,15 @@ class StudentInfoFragment :
|
||||
getString(R.string.student_info_correspondence_address) to studentInfo.correspondenceAddress
|
||||
).map {
|
||||
StudentInfoItem(
|
||||
it.first,
|
||||
it.second.ifBlank { getString(R.string.all_no_data) },
|
||||
false,
|
||||
title = it.first,
|
||||
subtitle = it.second.ifBlank { getString(R.string.all_no_data) },
|
||||
showArrow = false,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun showFirstGuardianTypeData(studentGuardian: StudentGuardian) {
|
||||
override fun showGuardianTypeData(studentGuardian: StudentGuardian) {
|
||||
updateData(
|
||||
listOf(
|
||||
getString(R.string.student_info_full_name) to studentGuardian.fullName,
|
||||
@ -196,27 +203,9 @@ class StudentInfoFragment :
|
||||
getString(R.string.student_info_email) to studentGuardian.email
|
||||
).map {
|
||||
StudentInfoItem(
|
||||
it.first,
|
||||
it.second.ifBlank { getString(R.string.all_no_data) },
|
||||
false,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun showSecondGuardianTypeData(studentGuardian: StudentGuardian) {
|
||||
updateData(
|
||||
listOf(
|
||||
getString(R.string.student_info_full_name) to studentGuardian.fullName,
|
||||
getString(R.string.student_info_kinship) to studentGuardian.kinship,
|
||||
getString(R.string.student_info_guardian_address) to studentGuardian.address,
|
||||
getString(R.string.student_info_phones) to studentGuardian.phones,
|
||||
getString(R.string.student_info_email) to studentGuardian.email
|
||||
).map {
|
||||
StudentInfoItem(
|
||||
it.first,
|
||||
it.second.ifBlank { getString(R.string.all_no_data) },
|
||||
false,
|
||||
title = it.first,
|
||||
subtitle = it.second.ifBlank { getString(R.string.all_no_data) },
|
||||
showArrow = false,
|
||||
)
|
||||
}
|
||||
)
|
||||
|
@ -3,5 +3,6 @@ package io.github.wulkanowy.ui.modules.studentinfo
|
||||
data class StudentInfoItem(
|
||||
val title: String,
|
||||
val subtitle: String,
|
||||
val showArrow: Boolean
|
||||
val showArrow: Boolean,
|
||||
val viewType: StudentInfoView.Type? = null,
|
||||
)
|
||||
|
@ -58,13 +58,12 @@ class StudentInfoPresenter @Inject constructor(
|
||||
view?.showErrorDetailsDialog(lastError)
|
||||
}
|
||||
|
||||
fun onItemSelected(position: Int) {
|
||||
if (infoType != StudentInfoView.Type.FAMILY) return
|
||||
fun onItemSelected(viewType: StudentInfoView.Type?) {
|
||||
viewType ?: return
|
||||
|
||||
view?.openStudentInfoView(
|
||||
if (position == 0) StudentInfoView.Type.FIRST_GUARDIAN
|
||||
else StudentInfoView.Type.SECOND_GUARDIAN,
|
||||
studentWithSemesters
|
||||
studentWithSemesters = studentWithSemesters,
|
||||
infoType = viewType,
|
||||
)
|
||||
}
|
||||
|
||||
@ -76,15 +75,19 @@ class StudentInfoPresenter @Inject constructor(
|
||||
flowWithResourceIn {
|
||||
val semester = studentWithSemesters.semesters.getCurrentOrLast()
|
||||
studentInfoRepository.getStudentInfo(
|
||||
studentWithSemesters.student,
|
||||
semester,
|
||||
forceRefresh
|
||||
student = studentWithSemesters.student,
|
||||
semester = semester,
|
||||
forceRefresh = forceRefresh
|
||||
)
|
||||
}.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> Timber.i("Loading student info $infoType started")
|
||||
Status.SUCCESS -> {
|
||||
if (it.data != null && !(infoType == StudentInfoView.Type.FAMILY && it.data.firstGuardian == null && it.data.secondGuardian == null)) {
|
||||
val isFamily = infoType == StudentInfoView.Type.FAMILY
|
||||
val isFirstGuardianEmpty = it.data?.firstGuardian == null
|
||||
val isSecondGuardianEmpty = it.data?.secondGuardian == null
|
||||
|
||||
if (it.data != null && !(isFamily && isFirstGuardianEmpty && isSecondGuardianEmpty)) {
|
||||
Timber.i("Loading student info $infoType result: Success")
|
||||
showCorrectData(it.data)
|
||||
view?.run {
|
||||
@ -122,8 +125,8 @@ class StudentInfoPresenter @Inject constructor(
|
||||
StudentInfoView.Type.CONTACT -> view?.showContactTypeData(studentInfo)
|
||||
StudentInfoView.Type.ADDRESS -> view?.showAddressTypeData(studentInfo)
|
||||
StudentInfoView.Type.FAMILY -> view?.showFamilyTypeData(studentInfo)
|
||||
StudentInfoView.Type.SECOND_GUARDIAN -> view?.showSecondGuardianTypeData(studentInfo.secondGuardian!!)
|
||||
StudentInfoView.Type.FIRST_GUARDIAN -> view?.showFirstGuardianTypeData(studentInfo.firstGuardian!!)
|
||||
StudentInfoView.Type.SECOND_GUARDIAN -> view?.showGuardianTypeData(studentInfo.secondGuardian!!)
|
||||
StudentInfoView.Type.FIRST_GUARDIAN -> view?.showGuardianTypeData(studentInfo.firstGuardian!!)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,9 +25,7 @@ interface StudentInfoView : BaseView {
|
||||
|
||||
fun showFamilyTypeData(studentInfo: StudentInfo)
|
||||
|
||||
fun showFirstGuardianTypeData(studentGuardian: StudentGuardian)
|
||||
|
||||
fun showSecondGuardianTypeData(studentGuardian: StudentGuardian)
|
||||
fun showGuardianTypeData(studentGuardian: StudentGuardian)
|
||||
|
||||
fun openStudentInfoView(infoType: Type, studentWithSemesters: StudentWithSemesters)
|
||||
|
||||
|
@ -7,7 +7,7 @@ import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.core.text.parseAsHtml
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.datepicker.CalendarConstraints
|
||||
import com.google.android.material.datepicker.MaterialDatePicker
|
||||
@ -49,7 +49,7 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
|
||||
|
||||
override val titleStringId get() = R.string.timetable_title
|
||||
|
||||
override val isViewEmpty get() = timetableAdapter.itemCount > 0
|
||||
override val isViewEmpty get() = timetableAdapter.itemCount == 0
|
||||
|
||||
override val currentStackSize get() = (activity as? MainActivity)?.currentStackSize
|
||||
|
||||
@ -147,9 +147,7 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
|
||||
|
||||
override fun setDayHeaderMessage(message: String?) {
|
||||
binding.timetableEmptyMessage.visibility = if (message.isNullOrEmpty()) GONE else VISIBLE
|
||||
binding.timetableEmptyMessage.text = HtmlCompat.fromHtml(
|
||||
message.orEmpty(), HtmlCompat.FROM_HTML_MODE_COMPACT
|
||||
)
|
||||
binding.timetableEmptyMessage.text = message.orEmpty().parseAsHtml()
|
||||
}
|
||||
|
||||
override fun showErrorView(show: Boolean) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package io.github.wulkanowy.utils
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
@ -58,8 +59,11 @@ fun Context.getCompatBitmap(@DrawableRes drawableRes: Int, @ColorRes colorRes: I
|
||||
|
||||
fun Context.openInternetBrowser(uri: String, onActivityNotFound: (uri: String) -> Unit = {}) {
|
||||
Intent.parseUri(uri, 0).let {
|
||||
if (it.resolveActivity(packageManager) != null) startActivity(it)
|
||||
else onActivityNotFound(uri)
|
||||
try {
|
||||
startActivity(it)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
onActivityNotFound(uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ inline val Timetable.left: Duration?
|
||||
get() = when {
|
||||
canceled -> null
|
||||
!isStudentPlan -> null
|
||||
end.isAfter(now()) && start.isBefore(now()) -> between(now(), end)
|
||||
end >= now() && start <= now() -> between(now(), end)
|
||||
else -> null
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,7 @@
|
||||
Wersja 1.2.0
|
||||
Wersja 1.2.1
|
||||
|
||||
- dodaliśmy nowy ekran startowy 🎉
|
||||
- usprawniliśmy powiadomienia
|
||||
- dodaliśmy wersje robocze, filtrowanie oraz informację o odczytaniu przez odbiorcę w wiadomościach
|
||||
- dodaliśmy informacje o liczeniu średniej w podsumowaniu ocen
|
||||
- dodaliśmy opcję generowania wiadomości z usprawiedliwieniem dni w szkołach pozbawionych funkcji usprawiedliwiania przez zakładkę frekwencja
|
||||
- oraz wiele wiele innych ulepszeń i poprawek
|
||||
- dodaliśmy brakujące okienka z podglądem szczegółów ogłoszeń szkolnych
|
||||
- naprawiliśmy rzucające się w oczy błędy na ekranie startowym
|
||||
- naprawiliśmy też inne drobne błędy w wyglądzie i stabilności aplikacji
|
||||
|
||||
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases
|
||||
|
148
app/src/main/res/layout/dialog_school_announcement.xml
Normal file
148
app/src/main/res/layout/dialog_school_announcement.xml
Normal file
@ -0,0 +1,148 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingEnd="8dp">
|
||||
|
||||
<View
|
||||
android:layout_width="280dp"
|
||||
android:layout_height="1dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/allDetailsHeader"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:text="@string/all_details"
|
||||
android:textSize="21sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/announcementDialogSubjectTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginTop="28dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:text="@string/all_subject"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/allDetailsHeader" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/announcementDialogSubjectValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:text="@string/all_no_data"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/announcementDialogSubjectTitle" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/announcementDialogDateTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:text="@string/exam_entry_date"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/announcementDialogSubjectValue" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/announcementDialogDateValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:text="@string/all_no_data"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/announcementDialogDateTitle" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/announcementDialogDescriptionTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:text="@string/all_description"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/announcementDialogDateValue" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/announcementDialogDescriptionValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:text="@string/all_no_data"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/announcementDialogDescriptionTitle"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/announcementDialogClose"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton.Dialog"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginTop="36dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
android:minWidth="88dp"
|
||||
android:text="@string/all_close"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/announcementDialogDescriptionValue" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
@ -36,7 +36,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
@ -44,91 +44,115 @@
|
||||
android:id="@+id/gradeStatisticsProgress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="gone" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/gradeStatisticsRecycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:listitem="@layout/item_grade_statistics_pie" />
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:listitem="@layout/item_grade_statistics_pie"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/gradeStatisticsEmpty"
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="invisible"
|
||||
tools:ignore="UseCompoundDrawables">
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:fillViewport="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/gradeStatisticsRecycler">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
app:srcCompat="@drawable/ic_main_grade"
|
||||
app:tint="?colorOnBackground"
|
||||
tools:ignore="contentDescription" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/grade_no_items"
|
||||
android:textSize="20sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/gradeStatisticsError"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="invisible"
|
||||
tools:ignore="UseCompoundDrawables"
|
||||
tools:visibility="invisible">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
app:srcCompat="@drawable/ic_error"
|
||||
app:tint="?colorOnBackground"
|
||||
tools:ignore="contentDescription" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/gradeStatisticsErrorMessage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center"
|
||||
android:padding="8dp"
|
||||
android:text="@string/error_unknown"
|
||||
android:textSize="20sp" />
|
||||
|
||||
<LinearLayout
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/gradeStatisticsErrorDetails"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="wrap_content"
|
||||
<LinearLayout
|
||||
android:id="@+id/gradeStatisticsEmpty"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:text="@string/all_details" />
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="UseCompoundDrawables"
|
||||
tools:visibility="gone">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/gradeStatisticsErrorRetry"
|
||||
android:layout_width="wrap_content"
|
||||
<ImageView
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
app:srcCompat="@drawable/ic_main_grade"
|
||||
app:tint="?colorOnBackground"
|
||||
tools:ignore="contentDescription" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/grade_no_items"
|
||||
android:textSize="20sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/gradeStatisticsError"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/all_retry" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="UseCompoundDrawables"
|
||||
tools:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
app:srcCompat="@drawable/ic_error"
|
||||
app:tint="?colorOnBackground"
|
||||
tools:ignore="contentDescription" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/gradeStatisticsErrorMessage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center"
|
||||
android:padding="8dp"
|
||||
android:text="@string/error_unknown"
|
||||
android:textSize="20sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/gradeStatisticsErrorDetails"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:text="@string/all_details" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/gradeStatisticsErrorRetry"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/all_retry" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
</LinearLayout>
|
||||
|
@ -4,14 +4,14 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:layout_marginVertical="2dp"
|
||||
android:clipToPadding="false">
|
||||
android:clipToPadding="false"
|
||||
android:paddingHorizontal="12dp">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/dashboard_horizontal_group_item_lucky_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="44dp"
|
||||
android:layout_marginVertical="4dp"
|
||||
app:cardElevation="4dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
@ -62,7 +62,7 @@
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/dashboard_horizontal_group_item_message_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="44dp"
|
||||
android:layout_marginVertical="4dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:cardElevation="4dp"
|
||||
@ -119,7 +119,7 @@
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/dashboard_horizontal_group_item_attendance_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="44dp"
|
||||
android:layout_marginVertical="4dp"
|
||||
app:cardElevation="4dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
@ -169,10 +169,8 @@
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/dashboard_horizontal_group_item_info_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="44dp"
|
||||
android:layout_marginVertical="4dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:visibility="gone"
|
||||
app:cardElevation="4dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
@ -496,7 +496,6 @@
|
||||
<!--Dashboard-->
|
||||
<string name="dashboard_timetable_title">Lekce</string>
|
||||
<string name="dashboard_timetable_title_tomorrow">(Zítra)</string>
|
||||
<string name="dashboard_timetable_lesson_value">%1$s (%2$s)</string>
|
||||
<string name="dashboard_timetable_first_lesson_title_moment">Za chvíli:</string>
|
||||
<string name="dashboard_timetable_first_lesson_title_soon">Brzy:</string>
|
||||
<string name="dashboard_timetable_first_lesson_title_first">První:</string>
|
||||
@ -566,7 +565,7 @@
|
||||
<item quantity="other">Ještě %1$d dalších setkání</item>
|
||||
</plurals>
|
||||
<string name="dashboard_horizontal_group_error">Při načítání dat došlo k chybě</string>
|
||||
<string name="dashboard_horizontal_group_no_lukcy_number">Žádné</string>
|
||||
<string name="dashboard_horizontal_group_no_data">Žádné</string>
|
||||
<!--Error dialog-->
|
||||
<string name="dialog_error_check_update">Zkontrolovat aktualizace</string>
|
||||
<string name="dialog_error_check_update_message">Před hlášením chyby zkontrolujte, zda je k dispozici aktualizace s opravou chyb</string>
|
||||
|
@ -432,7 +432,6 @@
|
||||
<!--Dashboard-->
|
||||
<string name="dashboard_timetable_title">Lektionen</string>
|
||||
<string name="dashboard_timetable_title_tomorrow">(Morgen)</string>
|
||||
<string name="dashboard_timetable_lesson_value">%1$s (%2$s)</string>
|
||||
<string name="dashboard_timetable_first_lesson_title_moment">Gleich:</string>
|
||||
<string name="dashboard_timetable_first_lesson_title_soon">Bald:</string>
|
||||
<string name="dashboard_timetable_first_lesson_title_first">Erstens:</string>
|
||||
@ -488,7 +487,7 @@
|
||||
<item quantity="other">%1$d weitere Konferenzen</item>
|
||||
</plurals>
|
||||
<string name="dashboard_horizontal_group_error">Fehler beim Laden der Daten</string>
|
||||
<string name="dashboard_horizontal_group_no_lukcy_number">Keine</string>
|
||||
<string name="dashboard_horizontal_group_no_data">Keine</string>
|
||||
<!--Error dialog-->
|
||||
<string name="dialog_error_check_update">Auf Updates prüfen</string>
|
||||
<string name="dialog_error_check_update_message">Bevor Sie einen Fehler melden, prüfen Sie zuerst, ob ein Update mit der Fehlerbehebung verfügbar ist</string>
|
||||
|
@ -53,7 +53,7 @@
|
||||
<string name="login_incorrect_symbol">Nie znaleziono ucznia. Sprawdź poprawność symbolu i wybranej odmiany dziennika UONET+</string>
|
||||
<string name="login_field_required">To pole jest wymagane</string>
|
||||
<string name="login_duplicate_student">Wybrany uczeń jest już zalogowany</string>
|
||||
<string name="login_symbol_helper">Symbol znajdziesz na stronie dziennika w <b>Uczeń</b> → <b>Dostęp Mobilny</b> → <b>Zarejestruj urządzenie mobilne</b>.\n\nUpewnij się, że w polu <b>Dziennik UONET+</b> na poprzednim ekranie została ustawiona odpowiednia odmiana dziennika. Wulkanowy na chwilę obecną nie wykrywa uczniów przedszkolnych</string>
|
||||
<string name="login_symbol_helper">Symbol znajdziesz na stronie dziennika w <b>Uczeń</b> → <b>Dostęp Mobilny</b> → <b>Zarejestruj urządzenie mobilne</b>.\n\nUpewnij się, że w polu <b>Dziennik UONET+</b> na poprzednim ekranie została ustawiona odpowiednia odmiana dziennika.\n\n<b>Wulkanowy na chwilę obecną nie wykrywa uczniów przedszkolnych (z zerówki)</b></string>
|
||||
<string name="login_select_student">Wybierz uczniów do zalogowania w aplikacji</string>
|
||||
<string name="login_advanced">Inne opcje</string>
|
||||
<string name="login_advanced_warning_mobile_api">W tym trybie nie działa szczęśliwy numerek, uczeń na tle klasy, podsumowanie frekwencji, usprawiedliwianie nieobecności, lekcje zrealizowane, informacje o szkole i podgląd listy zarejestrowanych urządzeń</string>
|
||||
@ -496,7 +496,6 @@
|
||||
<!--Dashboard-->
|
||||
<string name="dashboard_timetable_title">Lekcje</string>
|
||||
<string name="dashboard_timetable_title_tomorrow">(Jutro)</string>
|
||||
<string name="dashboard_timetable_lesson_value">%1$s (%2$s)</string>
|
||||
<string name="dashboard_timetable_first_lesson_title_moment">Za chwilę:</string>
|
||||
<string name="dashboard_timetable_first_lesson_title_soon">Wkrótce:</string>
|
||||
<string name="dashboard_timetable_first_lesson_title_first">Pierwsza:</string>
|
||||
@ -566,7 +565,7 @@
|
||||
<item quantity="other">Jeszcze %1$d dodatkowych zebrań</item>
|
||||
</plurals>
|
||||
<string name="dashboard_horizontal_group_error">Wystąpił błąd podczas ładowania danych</string>
|
||||
<string name="dashboard_horizontal_group_no_lukcy_number">Brak</string>
|
||||
<string name="dashboard_horizontal_group_no_data">Brak</string>
|
||||
<!--Error dialog-->
|
||||
<string name="dialog_error_check_update">Sprawdź dostępność aktualizacji</string>
|
||||
<string name="dialog_error_check_update_message">Przed zgłoszeniem błędu sprawdź wcześniej, czy dostępna jest już aktualizacja z poprawką błędu</string>
|
||||
|
@ -496,7 +496,6 @@
|
||||
<!--Dashboard-->
|
||||
<string name="dashboard_timetable_title">Уроки</string>
|
||||
<string name="dashboard_timetable_title_tomorrow">(Завтра)</string>
|
||||
<string name="dashboard_timetable_lesson_value">%1$s (%2$s)</string>
|
||||
<string name="dashboard_timetable_first_lesson_title_moment">Сейчас:</string>
|
||||
<string name="dashboard_timetable_first_lesson_title_soon">Скоро:</string>
|
||||
<string name="dashboard_timetable_first_lesson_title_first">Первый:</string>
|
||||
@ -566,7 +565,7 @@
|
||||
<item quantity="other">Еще %1$d конференций</item>
|
||||
</plurals>
|
||||
<string name="dashboard_horizontal_group_error">Произошла ошибка при загрузке данных</string>
|
||||
<string name="dashboard_horizontal_group_no_lukcy_number">Отсутствует</string>
|
||||
<string name="dashboard_horizontal_group_no_data">Отсутствует</string>
|
||||
<!--Error dialog-->
|
||||
<string name="dialog_error_check_update">Проверить наличие обновлений</string>
|
||||
<string name="dialog_error_check_update_message">Прежде чем сообщать об ошибке, проверьте наличие обновлений</string>
|
||||
|
@ -496,7 +496,6 @@
|
||||
<!--Dashboard-->
|
||||
<string name="dashboard_timetable_title">Lekcie</string>
|
||||
<string name="dashboard_timetable_title_tomorrow">(Zajtra)</string>
|
||||
<string name="dashboard_timetable_lesson_value">%1$s (%2$s)</string>
|
||||
<string name="dashboard_timetable_first_lesson_title_moment">Za chvíľu:</string>
|
||||
<string name="dashboard_timetable_first_lesson_title_soon">Čoskoro:</string>
|
||||
<string name="dashboard_timetable_first_lesson_title_first">Prvá:</string>
|
||||
@ -566,7 +565,7 @@
|
||||
<item quantity="other">Ešte %1$d ďalších stretnutí</item>
|
||||
</plurals>
|
||||
<string name="dashboard_horizontal_group_error">Pri načítaní dát došlo k chybe</string>
|
||||
<string name="dashboard_horizontal_group_no_lukcy_number">Žiadne</string>
|
||||
<string name="dashboard_horizontal_group_no_data">Žiadne</string>
|
||||
<!--Error dialog-->
|
||||
<string name="dialog_error_check_update">Skontrolovať aktualizácie</string>
|
||||
<string name="dialog_error_check_update_message">Pred hlásením chyby skontrolujte, či je k dispozícii aktualizácia s opravou chýb</string>
|
||||
|
@ -496,7 +496,6 @@
|
||||
<!--Dashboard-->
|
||||
<string name="dashboard_timetable_title">Уроки</string>
|
||||
<string name="dashboard_timetable_title_tomorrow">(Завтра)</string>
|
||||
<string name="dashboard_timetable_lesson_value">%1$s (%2$s)</string>
|
||||
<string name="dashboard_timetable_first_lesson_title_moment">Через мить:</string>
|
||||
<string name="dashboard_timetable_first_lesson_title_soon">Незабаром:</string>
|
||||
<string name="dashboard_timetable_first_lesson_title_first">Перше:</string>
|
||||
@ -566,7 +565,7 @@
|
||||
<item quantity="other">%1$d більше конференцій</item>
|
||||
</plurals>
|
||||
<string name="dashboard_horizontal_group_error">Помилка при завантаженні даних</string>
|
||||
<string name="dashboard_horizontal_group_no_lukcy_number">Нічого</string>
|
||||
<string name="dashboard_horizontal_group_no_data">Нічого</string>
|
||||
<!--Error dialog-->
|
||||
<string name="dialog_error_check_update">Провірити наявність оновлень</string>
|
||||
<string name="dialog_error_check_update_message">Перед тим, як повідомлювати о помілці, перевірте наявність оновлень</string>
|
||||
|
@ -495,7 +495,6 @@
|
||||
<!--Dashboard-->
|
||||
<string name="dashboard_timetable_title">Lessons</string>
|
||||
<string name="dashboard_timetable_title_tomorrow">(Tomorrow)</string>
|
||||
<string name="dashboard_timetable_lesson_value">%1$s (%2$s)</string>
|
||||
<string name="dashboard_timetable_first_lesson_title_moment">In a moment:</string>
|
||||
<string name="dashboard_timetable_first_lesson_title_soon">Soon:</string>
|
||||
<string name="dashboard_timetable_first_lesson_title_first">First:</string>
|
||||
@ -557,7 +556,7 @@
|
||||
</plurals>
|
||||
|
||||
<string name="dashboard_horizontal_group_error">An error occurred while loading data</string>
|
||||
<string name="dashboard_horizontal_group_no_lukcy_number">None</string>
|
||||
<string name="dashboard_horizontal_group_no_data">None</string>
|
||||
|
||||
|
||||
<!--Error dialog-->
|
||||
|
@ -32,7 +32,22 @@ class TimetableExtensionTest {
|
||||
assertEquals(null, getTimetableEntity(canceled = true).left)
|
||||
assertEquals(null, getTimetableEntity(start = now().plusMinutes(5), end = now().plusMinutes(50)).left)
|
||||
assertEquals(null, getTimetableEntity(start = now().minusMinutes(1), end = now().plusMinutes(44), isStudentPlan = false).left)
|
||||
assertNotEquals(null, getTimetableEntity(start = now().minusMinutes(1), end = now().plusMinutes(44), isStudentPlan = true).left)
|
||||
assertNotEquals(
|
||||
null,
|
||||
getTimetableEntity(
|
||||
start = now().minusMinutes(1),
|
||||
end = now().plusMinutes(44),
|
||||
isStudentPlan = true
|
||||
).left
|
||||
)
|
||||
assertNotEquals(
|
||||
null,
|
||||
getTimetableEntity(
|
||||
start = now(),
|
||||
end = now().plusMinutes(45),
|
||||
isStudentPlan = true
|
||||
).left
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -12,13 +12,13 @@ buildscript {
|
||||
}
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath 'com.android.tools.build:gradle:7.0.1'
|
||||
classpath 'com.android.tools.build:gradle:7.0.2'
|
||||
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
|
||||
classpath 'com.google.gms:google-services:4.3.10'
|
||||
classpath 'com.huawei.agconnect:agcp:1.6.0.300'
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.7.1'
|
||||
classpath "com.github.triplet.gradle:play-publisher:2.8.0"
|
||||
classpath "ru.cian:huawei-publish-gradle-plugin:1.2.4"
|
||||
classpath "ru.cian:huawei-publish-gradle-plugin:1.3.0"
|
||||
classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3"
|
||||
classpath "gradle.plugin.com.star-zero.gradle:githook:1.2.0"
|
||||
classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libraries"
|
||||
|
Loading…
x
Reference in New Issue
Block a user