forked from github/wulkanowy-mirror
Add additional lessons to timetable (#1019)
This commit is contained in:
parent
295fd0fd90
commit
9763208688
@ -142,7 +142,7 @@ configurations.all {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "io.github.wulkanowy:sdk:0.23.1"
|
||||
implementation "io.github.wulkanowy:sdk:6edc8531"
|
||||
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
|
||||
|
||||
|
1954
app/schemas/io.github.wulkanowy.data.db.AppDatabase/30.json
Normal file
1954
app/schemas/io.github.wulkanowy.data.db.AppDatabase/30.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -26,7 +26,7 @@ class TimetableLocalTest {
|
||||
fun createDb() {
|
||||
testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java)
|
||||
.build()
|
||||
timetableDb = TimetableLocal(testDb.timetableDao)
|
||||
timetableDb = TimetableLocal(testDb.timetableDao, testDb.timetableAdditionalDao)
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -46,7 +46,7 @@ class TimetableRepositoryTest {
|
||||
fun initApi() {
|
||||
MockKAnnotations.init(this)
|
||||
testDb = Room.inMemoryDatabaseBuilder(getApplicationContext(), AppDatabase::class.java).build()
|
||||
timetableLocal = TimetableLocal(testDb.timetableDao)
|
||||
timetableLocal = TimetableLocal(testDb.timetableDao, testDb.timetableAdditionalDao)
|
||||
}
|
||||
|
||||
@After
|
||||
@ -65,12 +65,12 @@ class TimetableRepositoryTest {
|
||||
))
|
||||
}
|
||||
|
||||
coEvery { timetableRemote.getTimetable(student, semester, any(), any()) } returns listOf(
|
||||
coEvery { timetableRemote.getTimetable(student, semester, any(), any()) } returns (listOf(
|
||||
createTimetableLocal(of(2019, 3, 5, 8, 0), 1, "", "Przyroda"),
|
||||
createTimetableLocal(of(2019, 3, 5, 8, 50), 2, "", "Religia"),
|
||||
createTimetableLocal(of(2019, 3, 5, 9, 40), 3, "", "W-F"),
|
||||
createTimetableLocal(of(2019, 3, 5, 10, 30), 4, "", "W-F")
|
||||
)
|
||||
) to emptyList())
|
||||
|
||||
val lessons = runBlocking {
|
||||
TimetableRepository(timetableLocal, timetableRemote, timetableNotificationSchedulerHelper).getTimetable(
|
||||
@ -79,7 +79,7 @@ class TimetableRepositoryTest {
|
||||
start = LocalDate.of(2019, 3, 5),
|
||||
end = LocalDate.of(2019, 3, 5),
|
||||
forceRefresh = true
|
||||
).filter { it.status == Status.SUCCESS }.first().data.orEmpty()
|
||||
).filter { it.status == Status.SUCCESS }.first().data?.first.orEmpty()
|
||||
}
|
||||
|
||||
assertEquals(4, lessons.size)
|
||||
@ -108,7 +108,7 @@ class TimetableRepositoryTest {
|
||||
)
|
||||
runBlocking { timetableLocal.saveTimetable(list) }
|
||||
|
||||
coEvery { timetableRemote.getTimetable(student, semester, any(), any()) } returns listOf(
|
||||
coEvery { timetableRemote.getTimetable(student, semester, any(), any()) } returns (listOf(
|
||||
createTimetableLocal(of(2019, 12, 23, 8, 0), 1, "123", "Matematyka", "Paweł Poniedziałkowski", false),
|
||||
createTimetableLocal(of(2019, 12, 23, 8, 50), 2, "124", "Matematyka", "Jakub Wtorkowski", true),
|
||||
createTimetableLocal(of(2019, 12, 23, 9, 40), 3, "125", "Język polski", "Joanna Poniedziałkowska", false),
|
||||
@ -123,7 +123,7 @@ class TimetableRepositoryTest {
|
||||
createTimetableLocal(of(2019, 12, 25, 8, 50), 2, "124", "Matematyka", "Paweł Czwartkowski", true),
|
||||
createTimetableLocal(of(2019, 12, 25, 9, 40), 3, "125", "Matematyka", "Paweł Środowski", false),
|
||||
createTimetableLocal(of(2019, 12, 25, 10, 40), 4, "126", "Matematyka", "Paweł Czwartkowski", true)
|
||||
)
|
||||
) to emptyList())
|
||||
|
||||
val lessons = runBlocking {
|
||||
TimetableRepository(timetableLocal, timetableRemote, timetableNotificationSchedulerHelper).getTimetable(
|
||||
@ -132,7 +132,7 @@ class TimetableRepositoryTest {
|
||||
start = LocalDate.of(2019, 12, 23),
|
||||
end = LocalDate.of(2019, 12, 25),
|
||||
forceRefresh = true
|
||||
).filter { it.status == Status.SUCCESS }.first().data.orEmpty()
|
||||
).filter { it.status == Status.SUCCESS }.first().data?.first.orEmpty()
|
||||
}
|
||||
|
||||
assertEquals(12, lessons.size)
|
||||
|
@ -162,4 +162,8 @@ internal class RepositoryModule {
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideConferenceDao(database: AppDatabase) = database.conferenceDao
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideTimetableAdditionalDao(database: AppDatabase) = database.timetableAdditionalDao
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import io.github.wulkanowy.data.db.dao.SemesterDao
|
||||
import io.github.wulkanowy.data.db.dao.StudentDao
|
||||
import io.github.wulkanowy.data.db.dao.SubjectDao
|
||||
import io.github.wulkanowy.data.db.dao.TeacherDao
|
||||
import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
|
||||
import io.github.wulkanowy.data.db.dao.TimetableDao
|
||||
import io.github.wulkanowy.data.db.entities.Attendance
|
||||
import io.github.wulkanowy.data.db.entities.AttendanceSummary
|
||||
@ -55,6 +56,7 @@ import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.Subject
|
||||
import io.github.wulkanowy.data.db.entities.Teacher
|
||||
import io.github.wulkanowy.data.db.entities.Timetable
|
||||
import io.github.wulkanowy.data.db.entities.TimetableAdditional
|
||||
import io.github.wulkanowy.data.db.migrations.Migration10
|
||||
import io.github.wulkanowy.data.db.migrations.Migration11
|
||||
import io.github.wulkanowy.data.db.migrations.Migration12
|
||||
@ -77,6 +79,7 @@ import io.github.wulkanowy.data.db.migrations.Migration27
|
||||
import io.github.wulkanowy.data.db.migrations.Migration28
|
||||
import io.github.wulkanowy.data.db.migrations.Migration29
|
||||
import io.github.wulkanowy.data.db.migrations.Migration3
|
||||
import io.github.wulkanowy.data.db.migrations.Migration30
|
||||
import io.github.wulkanowy.data.db.migrations.Migration4
|
||||
import io.github.wulkanowy.data.db.migrations.Migration5
|
||||
import io.github.wulkanowy.data.db.migrations.Migration6
|
||||
@ -112,6 +115,7 @@ import javax.inject.Singleton
|
||||
Teacher::class,
|
||||
School::class,
|
||||
Conference::class,
|
||||
TimetableAdditional::class,
|
||||
],
|
||||
version = AppDatabase.VERSION_SCHEMA,
|
||||
exportSchema = true
|
||||
@ -120,7 +124,7 @@ import javax.inject.Singleton
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
companion object {
|
||||
const val VERSION_SCHEMA = 29
|
||||
const val VERSION_SCHEMA = 30
|
||||
|
||||
fun getMigrations(sharedPrefProvider: SharedPrefProvider): Array<Migration> {
|
||||
return arrayOf(
|
||||
@ -151,7 +155,8 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
Migration26(),
|
||||
Migration27(),
|
||||
Migration28(),
|
||||
Migration29()
|
||||
Migration29(),
|
||||
Migration30(),
|
||||
)
|
||||
}
|
||||
|
||||
@ -212,4 +217,6 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
abstract val schoolDao: SchoolDao
|
||||
|
||||
abstract val conferenceDao: ConferenceDao
|
||||
|
||||
abstract val timetableAdditionalDao: TimetableAdditionalDao
|
||||
}
|
||||
|
@ -0,0 +1,16 @@
|
||||
package io.github.wulkanowy.data.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Query
|
||||
import io.github.wulkanowy.data.db.entities.TimetableAdditional
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Dao
|
||||
@Singleton
|
||||
interface TimetableAdditionalDao : BaseDao<TimetableAdditional> {
|
||||
|
||||
@Query("SELECT * FROM TimetableAdditional WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
|
||||
fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Flow<List<TimetableAdditional>>
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package io.github.wulkanowy.data.db.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import java.io.Serializable
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Entity(tableName = "TimetableAdditional")
|
||||
data class TimetableAdditional(
|
||||
|
||||
@ColumnInfo(name = "student_id")
|
||||
val studentId: Int,
|
||||
|
||||
@ColumnInfo(name = "diary_id")
|
||||
val diaryId: Int,
|
||||
|
||||
val start: LocalDateTime,
|
||||
|
||||
val end: LocalDateTime,
|
||||
|
||||
val date: LocalDate,
|
||||
|
||||
val subject: String,
|
||||
) : Serializable {
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Long = 0
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package io.github.wulkanowy.data.db.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
class Migration30 : Migration(29, 30) {
|
||||
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("""
|
||||
CREATE TABLE TimetableAdditional (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
student_id INTEGER NOT NULL,
|
||||
diary_id INTEGER NOT NULL,
|
||||
start INTEGER NOT NULL,
|
||||
`end` INTEGER NOT NULL,
|
||||
date INTEGER NOT NULL,
|
||||
subject TEXT NOT NULL
|
||||
)
|
||||
""")
|
||||
}
|
||||
}
|
@ -21,11 +21,15 @@ class GradeRepository @Inject constructor(
|
||||
|
||||
fun getGrades(student: Student, semester: Semester, forceRefresh: Boolean, notify: Boolean = false) = networkBoundResource(
|
||||
shouldFetch = { (details, summaries) -> details.isEmpty() || summaries.isEmpty() || forceRefresh },
|
||||
query = { local.getGradesDetails(semester).combine(local.getGradesSummary(semester)) { details, summaries -> details to summaries } },
|
||||
query = {
|
||||
local.getGradesDetails(semester).combine(local.getGradesSummary(semester)) { details, summaries ->
|
||||
details to summaries
|
||||
}
|
||||
},
|
||||
fetch = { remote.getGrades(student, semester) },
|
||||
saveFetchResult = { old, new ->
|
||||
refreshGradeDetails(student, old.first, new.first, notify)
|
||||
refreshGradeSummaries(old.second, new.second, notify)
|
||||
saveFetchResult = { (oldDetails, oldSummary), (newDetails, newSummary) ->
|
||||
refreshGradeDetails(student, oldDetails, newDetails, notify)
|
||||
refreshGradeSummaries(oldSummary, newSummary, notify)
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -1,25 +1,42 @@
|
||||
package io.github.wulkanowy.data.repositories.timetable
|
||||
|
||||
import io.github.wulkanowy.data.db.dao.TimetableAdditionalDao
|
||||
import io.github.wulkanowy.data.db.dao.TimetableDao
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Timetable
|
||||
import io.github.wulkanowy.data.db.entities.TimetableAdditional
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class TimetableLocal @Inject constructor(private val timetableDb: TimetableDao) {
|
||||
class TimetableLocal @Inject constructor(
|
||||
private val timetableDb: TimetableDao,
|
||||
private val timetableAdditionalDb: TimetableAdditionalDao
|
||||
) {
|
||||
|
||||
suspend fun saveTimetable(timetables: List<Timetable>) {
|
||||
timetableDb.insertAll(timetables)
|
||||
}
|
||||
|
||||
suspend fun saveTimetableAdditional(additional: List<TimetableAdditional>) {
|
||||
timetableAdditionalDb.insertAll(additional)
|
||||
}
|
||||
|
||||
suspend fun deleteTimetable(timetables: List<Timetable>) {
|
||||
timetableDb.deleteAll(timetables)
|
||||
}
|
||||
|
||||
suspend fun deleteTimetableAdditional(additional: List<TimetableAdditional>) {
|
||||
timetableAdditionalDb.deleteAll(additional)
|
||||
}
|
||||
|
||||
fun getTimetable(semester: Semester, startDate: LocalDate, endDate: LocalDate): Flow<List<Timetable>> {
|
||||
return timetableDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate)
|
||||
}
|
||||
|
||||
fun getTimetableAdditional(semester: Semester, startDate: LocalDate, endDate: LocalDate): Flow<List<TimetableAdditional>> {
|
||||
return timetableAdditionalDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate)
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package io.github.wulkanowy.data.repositories.timetable
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.Timetable
|
||||
import io.github.wulkanowy.data.db.entities.TimetableAdditional
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
import io.github.wulkanowy.utils.init
|
||||
import java.time.LocalDate
|
||||
@ -12,29 +13,40 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class TimetableRemote @Inject constructor(private val sdk: Sdk) {
|
||||
|
||||
suspend fun getTimetable(student: Student, semester: Semester, startDate: LocalDate, endDate: LocalDate): List<Timetable> {
|
||||
return sdk.init(student).switchDiary(semester.diaryId, semester.schoolYear)
|
||||
.getTimetable(startDate, endDate).first
|
||||
.map {
|
||||
Timetable(
|
||||
studentId = semester.studentId,
|
||||
diaryId = semester.diaryId,
|
||||
number = it.number,
|
||||
start = it.start,
|
||||
end = it.end,
|
||||
date = it.date,
|
||||
subject = it.subject,
|
||||
subjectOld = it.subjectOld,
|
||||
group = it.group,
|
||||
room = it.room,
|
||||
roomOld = it.roomOld,
|
||||
teacher = it.teacher,
|
||||
teacherOld = it.teacherOld,
|
||||
info = it.info,
|
||||
isStudentPlan = it.studentPlan,
|
||||
changes = it.changes,
|
||||
canceled = it.canceled
|
||||
)
|
||||
}
|
||||
suspend fun getTimetable(student: Student, semester: Semester, startDate: LocalDate, endDate: LocalDate): Pair<List<Timetable>, List<TimetableAdditional>> {
|
||||
val (normal, additional) = sdk.init(student)
|
||||
.switchDiary(semester.diaryId, semester.schoolYear)
|
||||
.getTimetable(startDate, endDate)
|
||||
|
||||
return normal.map {
|
||||
Timetable(
|
||||
studentId = semester.studentId,
|
||||
diaryId = semester.diaryId,
|
||||
number = it.number,
|
||||
start = it.start,
|
||||
end = it.end,
|
||||
date = it.date,
|
||||
subject = it.subject,
|
||||
subjectOld = it.subjectOld,
|
||||
group = it.group,
|
||||
room = it.room,
|
||||
roomOld = it.roomOld,
|
||||
teacher = it.teacher,
|
||||
teacherOld = it.teacherOld,
|
||||
info = it.info,
|
||||
isStudentPlan = it.studentPlan,
|
||||
changes = it.changes,
|
||||
canceled = it.canceled
|
||||
)
|
||||
} to additional.map {
|
||||
TimetableAdditional(
|
||||
studentId = semester.studentId,
|
||||
diaryId = semester.diaryId,
|
||||
subject = it.subject,
|
||||
date = it.date,
|
||||
start = it.start,
|
||||
end = it.end
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,14 @@ package io.github.wulkanowy.data.repositories.timetable
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.Semester
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.db.entities.Timetable
|
||||
import io.github.wulkanowy.data.db.entities.TimetableAdditional
|
||||
import io.github.wulkanowy.services.alarm.TimetableNotificationSchedulerHelper
|
||||
import io.github.wulkanowy.utils.monday
|
||||
import io.github.wulkanowy.utils.networkBoundResource
|
||||
import io.github.wulkanowy.utils.sunday
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
@ -19,23 +22,44 @@ class TimetableRepository @Inject constructor(
|
||||
private val schedulerHelper: TimetableNotificationSchedulerHelper
|
||||
) {
|
||||
|
||||
fun getTimetable(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean) = networkBoundResource(
|
||||
shouldFetch = { it.isEmpty() || forceRefresh },
|
||||
query = { local.getTimetable(semester, start.monday, end.sunday).map { schedulerHelper.scheduleNotifications(it, student); it } },
|
||||
fetch = { remote.getTimetable(student, semester, start.monday, end.sunday) },
|
||||
saveFetchResult = { old, new ->
|
||||
local.deleteTimetable(old.uniqueSubtract(new).also { schedulerHelper.cancelScheduled(it) })
|
||||
local.saveTimetable(new.uniqueSubtract(old).also { schedulerHelper.scheduleNotifications(it, student) }.map { item ->
|
||||
item.also { new ->
|
||||
old.singleOrNull { new.start == it.start }?.let { old ->
|
||||
return@map new.copy(
|
||||
room = if (new.room.isEmpty()) old.room else new.room,
|
||||
teacher = if (new.teacher.isEmpty() && !new.changes && !old.changes) old.teacher else new.teacher
|
||||
)
|
||||
}
|
||||
fun getTimetable(student: Student, semester: Semester, start: LocalDate, end: LocalDate, forceRefresh: Boolean, refreshAdditional: Boolean = false) = networkBoundResource(
|
||||
shouldFetch = { (timetable, additional) -> timetable.isEmpty() || (additional.isEmpty() && refreshAdditional) || forceRefresh },
|
||||
query = {
|
||||
local.getTimetable(semester, start.monday, end.sunday)
|
||||
.map { schedulerHelper.scheduleNotifications(it, student); it }
|
||||
.combine(local.getTimetableAdditional(semester, start.monday, end.sunday)) { timetable, additional ->
|
||||
timetable to additional
|
||||
}
|
||||
})
|
||||
},
|
||||
filterResult = { it.filter { item -> item.date in start..end } }
|
||||
fetch = { remote.getTimetable(student, semester, start.monday, end.sunday) },
|
||||
saveFetchResult = { (oldTimetable, oldAdditional), (newTimetable, newAdditional) ->
|
||||
refreshTimetable(student, oldTimetable, newTimetable)
|
||||
refreshAdditional(oldAdditional, newAdditional)
|
||||
|
||||
},
|
||||
filterResult = { (timetable, additional) ->
|
||||
timetable.filter { item ->
|
||||
item.date in start..end
|
||||
} to additional.filter { item -> item.date in start..end }
|
||||
}
|
||||
)
|
||||
|
||||
private suspend fun refreshTimetable(student: Student, old: List<Timetable>, new: List<Timetable>) {
|
||||
local.deleteTimetable(old.uniqueSubtract(new).also { schedulerHelper.cancelScheduled(it) })
|
||||
local.saveTimetable(new.uniqueSubtract(old).also { schedulerHelper.scheduleNotifications(it, student) }.map { item ->
|
||||
item.also { new ->
|
||||
old.singleOrNull { new.start == it.start }?.let { old ->
|
||||
return@map new.copy(
|
||||
room = if (new.room.isEmpty()) old.room else new.room,
|
||||
teacher = if (new.teacher.isEmpty() && !new.changes && !old.changes) old.teacher else new.teacher
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private suspend fun refreshAdditional(old: List<TimetableAdditional>, new: List<TimetableAdditional>) {
|
||||
local.deleteTimetableAdditional(old.uniqueSubtract(new))
|
||||
local.saveTimetableAdditional(new.uniqueSubtract(old))
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import io.github.wulkanowy.databinding.FragmentTimetableBinding
|
||||
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.modules.timetable.additional.AdditionalLessonsFragment
|
||||
import io.github.wulkanowy.ui.modules.timetable.completed.CompletedLessonsFragment
|
||||
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
||||
import io.github.wulkanowy.utils.SchooldaysRangeLimiter
|
||||
@ -84,8 +85,11 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return if (item.itemId == R.id.timetableMenuCompletedLessons) presenter.onCompletedLessonsSwitchSelected()
|
||||
else false
|
||||
return when (item.itemId) {
|
||||
R.id.timetableMenuAdditionalLessons -> presenter.onAdditionalLessonsSwitchSelected()
|
||||
R.id.timetableMenuCompletedLessons -> presenter.onCompletedLessonsSwitchSelected()
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateData(data: List<Timetable>, showWholeClassPlanType: String, showGroupsInPlanType: Boolean, showTimetableTimers: Boolean) {
|
||||
@ -176,6 +180,10 @@ class TimetableFragment : BaseFragment<FragmentTimetableBinding>(R.layout.fragme
|
||||
}
|
||||
}
|
||||
|
||||
override fun openAdditionalLessonsView() {
|
||||
(activity as? MainActivity)?.pushView(AdditionalLessonsFragment.newInstance())
|
||||
}
|
||||
|
||||
override fun openCompletedLessonsView() {
|
||||
(activity as? MainActivity)?.pushView(CompletedLessonsFragment.newInstance())
|
||||
}
|
||||
|
@ -109,6 +109,11 @@ class TimetablePresenter @Inject constructor(
|
||||
view?.showTimetableDialog(lesson)
|
||||
}
|
||||
|
||||
fun onAdditionalLessonsSwitchSelected(): Boolean {
|
||||
view?.openAdditionalLessonsView()
|
||||
return true
|
||||
}
|
||||
|
||||
fun onCompletedLessonsSwitchSelected(): Boolean {
|
||||
view?.openCompletedLessonsView()
|
||||
return true
|
||||
@ -144,18 +149,18 @@ class TimetablePresenter @Inject constructor(
|
||||
showWholeClassPlanType = prefRepository.showWholeClassPlan,
|
||||
showGroupsInPlanType = prefRepository.showGroupsInPlan,
|
||||
showTimetableTimers = prefRepository.showTimetableTimers,
|
||||
data = it.data!!
|
||||
data = it.data!!.first
|
||||
.filter { item -> if (prefRepository.showWholeClassPlan == "no") item.isStudentPlan else true }
|
||||
.sortedWith(compareBy({ item -> item.number }, { item -> !item.isStudentPlan }))
|
||||
)
|
||||
showEmpty(it.data.isEmpty())
|
||||
showEmpty(it.data.first.isEmpty())
|
||||
showErrorView(false)
|
||||
showContent(it.data.isNotEmpty())
|
||||
showContent(it.data.first.isNotEmpty())
|
||||
}
|
||||
analytics.logEvent(
|
||||
"load_data",
|
||||
"type" to "timetable",
|
||||
"items" to it.data!!.size
|
||||
"items" to it.data!!.first.size
|
||||
)
|
||||
}
|
||||
Status.ERROR -> {
|
||||
|
@ -44,5 +44,7 @@ interface TimetableView : BaseView {
|
||||
|
||||
fun popView()
|
||||
|
||||
fun openAdditionalLessonsView()
|
||||
|
||||
fun openCompletedLessonsView()
|
||||
}
|
||||
|
@ -0,0 +1,35 @@
|
||||
package io.github.wulkanowy.ui.modules.timetable.additional
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.github.wulkanowy.data.db.entities.TimetableAdditional
|
||||
import io.github.wulkanowy.databinding.ItemTimetableAdditionalBinding
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import javax.inject.Inject
|
||||
|
||||
class AdditionalLessonsAdapter @Inject constructor() :
|
||||
RecyclerView.Adapter<AdditionalLessonsAdapter.ItemViewHolder>() {
|
||||
|
||||
var items = emptyList<TimetableAdditional>()
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
|
||||
ItemTimetableAdditionalBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
)
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
|
||||
val item = items[position]
|
||||
|
||||
with(holder.binding) {
|
||||
additionalLessonItemTime.text = "${item.start.toFormattedString("HH:mm")} - ${item.end.toFormattedString("HH:mm")}"
|
||||
additionalLessonItemSubject.text = item.subject
|
||||
}
|
||||
}
|
||||
|
||||
class ItemViewHolder(val binding: ItemTimetableAdditionalBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
}
|
@ -0,0 +1,144 @@
|
||||
package io.github.wulkanowy.ui.modules.timetable.additional
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.wdullaer.materialdatetimepicker.date.DatePickerDialog
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.github.wulkanowy.R
|
||||
import io.github.wulkanowy.data.db.entities.TimetableAdditional
|
||||
import io.github.wulkanowy.databinding.FragmentTimetableAdditionalBinding
|
||||
import io.github.wulkanowy.ui.base.BaseFragment
|
||||
import io.github.wulkanowy.ui.modules.main.MainView
|
||||
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
|
||||
import io.github.wulkanowy.utils.SchooldaysRangeLimiter
|
||||
import io.github.wulkanowy.utils.dpToPx
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AdditionalLessonsFragment :
|
||||
BaseFragment<FragmentTimetableAdditionalBinding>(R.layout.fragment_timetable_additional),
|
||||
AdditionalLessonsView, MainView.TitledView {
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: AdditionalLessonsPresenter
|
||||
|
||||
@Inject
|
||||
lateinit var additionalLessonsAdapter: AdditionalLessonsAdapter
|
||||
|
||||
companion object {
|
||||
private const val SAVED_DATE_KEY = "CURRENT_DATE"
|
||||
|
||||
fun newInstance() = AdditionalLessonsFragment()
|
||||
}
|
||||
|
||||
override val titleStringId get() = R.string.additional_lessons_title
|
||||
|
||||
override val isViewEmpty get() = additionalLessonsAdapter.items.isEmpty()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding = FragmentTimetableAdditionalBinding.bind(view)
|
||||
messageContainer = binding.additionalLessonsRecycler
|
||||
presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY))
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
with(binding.additionalLessonsRecycler) {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
adapter = additionalLessonsAdapter
|
||||
addItemDecoration(DividerItemDecoration(context))
|
||||
}
|
||||
|
||||
with(binding) {
|
||||
additionalLessonsSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
|
||||
additionalLessonsErrorRetry.setOnClickListener { presenter.onRetry() }
|
||||
|
||||
additionalLessonsPreviousButton.setOnClickListener { presenter.onPreviousDay() }
|
||||
additionalLessonsNavDate.setOnClickListener { presenter.onPickDate() }
|
||||
additionalLessonsNextButton.setOnClickListener { presenter.onNextDay() }
|
||||
|
||||
additionalLessonsNavContainer.setElevationCompat(requireContext().dpToPx(8f))
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateData(data: List<TimetableAdditional>) {
|
||||
with(additionalLessonsAdapter) {
|
||||
items = data
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun clearData() {
|
||||
with(additionalLessonsAdapter) {
|
||||
items = emptyList()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateNavigationDay(date: String) {
|
||||
binding.additionalLessonsNavDate.text = date
|
||||
}
|
||||
|
||||
override fun hideRefresh() {
|
||||
binding.additionalLessonsSwipe.isRefreshing = false
|
||||
}
|
||||
|
||||
override fun showEmpty(show: Boolean) {
|
||||
binding.additionalLessonsEmpty.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun showErrorView(show: Boolean) {
|
||||
binding.additionalLessonsError.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun setErrorDetails(message: String) {
|
||||
binding.additionalLessonsErrorMessage.text = message
|
||||
}
|
||||
|
||||
override fun showProgress(show: Boolean) {
|
||||
binding.additionalLessonsProgress.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun enableSwipe(enable: Boolean) {
|
||||
binding.additionalLessonsSwipe.isEnabled = enable
|
||||
}
|
||||
|
||||
override fun showContent(show: Boolean) {
|
||||
binding.additionalLessonsRecycler.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun showPreButton(show: Boolean) {
|
||||
binding.additionalLessonsPreviousButton.visibility = if (show) View.VISIBLE else View.INVISIBLE
|
||||
}
|
||||
|
||||
override fun showNextButton(show: Boolean) {
|
||||
binding.additionalLessonsNextButton.visibility = if (show) View.VISIBLE else View.INVISIBLE
|
||||
}
|
||||
|
||||
override fun showDatePickerDialog(currentDate: LocalDate) {
|
||||
val dateSetListener = DatePickerDialog.OnDateSetListener { _, year, month, dayOfMonth ->
|
||||
presenter.onDateSet(year, month + 1, dayOfMonth)
|
||||
}
|
||||
val datePickerDialog = DatePickerDialog.newInstance(dateSetListener,
|
||||
currentDate.year, currentDate.monthValue - 1, currentDate.dayOfMonth)
|
||||
|
||||
with(datePickerDialog) {
|
||||
setDateRangeLimiter(SchooldaysRangeLimiter())
|
||||
version = DatePickerDialog.Version.VERSION_2
|
||||
scrollOrientation = DatePickerDialog.ScrollOrientation.VERTICAL
|
||||
show(this@AdditionalLessonsFragment.parentFragmentManager, null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay())
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
presenter.onDetachView()
|
||||
super.onDestroyView()
|
||||
}
|
||||
}
|
@ -0,0 +1,166 @@
|
||||
package io.github.wulkanowy.ui.modules.timetable.additional
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import io.github.wulkanowy.data.Status
|
||||
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
|
||||
import io.github.wulkanowy.data.repositories.student.StudentRepository
|
||||
import io.github.wulkanowy.data.repositories.timetable.TimetableRepository
|
||||
import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||
import io.github.wulkanowy.utils.afterLoading
|
||||
import io.github.wulkanowy.utils.flowWithResourceIn
|
||||
import io.github.wulkanowy.utils.getLastSchoolDayIfHoliday
|
||||
import io.github.wulkanowy.utils.isHolidays
|
||||
import io.github.wulkanowy.utils.nextOrSameSchoolDay
|
||||
import io.github.wulkanowy.utils.nextSchoolDay
|
||||
import io.github.wulkanowy.utils.previousSchoolDay
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import timber.log.Timber
|
||||
import java.time.LocalDate
|
||||
import javax.inject.Inject
|
||||
|
||||
class AdditionalLessonsPresenter @Inject constructor(
|
||||
studentRepository: StudentRepository,
|
||||
errorHandler: ErrorHandler,
|
||||
private val semesterRepository: SemesterRepository,
|
||||
private val timetableRepository: TimetableRepository,
|
||||
private val analytics: AnalyticsHelper
|
||||
) : BasePresenter<AdditionalLessonsView>(errorHandler, studentRepository) {
|
||||
|
||||
private var baseDate: LocalDate = LocalDate.now().nextOrSameSchoolDay
|
||||
|
||||
lateinit var currentDate: LocalDate
|
||||
private set
|
||||
|
||||
private lateinit var lastError: Throwable
|
||||
|
||||
fun onAttachView(view: AdditionalLessonsView, date: Long?) {
|
||||
super.onAttachView(view)
|
||||
view.initView()
|
||||
Timber.i("Additional lessons was initialized")
|
||||
errorHandler.showErrorMessage = ::showErrorViewOnError
|
||||
loadData(LocalDate.ofEpochDay(date ?: baseDate.toEpochDay()))
|
||||
if (currentDate.isHolidays) setBaseDateOnHolidays()
|
||||
reloadView()
|
||||
}
|
||||
|
||||
fun onPreviousDay() {
|
||||
loadData(currentDate.previousSchoolDay)
|
||||
reloadView()
|
||||
}
|
||||
|
||||
fun onNextDay() {
|
||||
loadData(currentDate.nextSchoolDay)
|
||||
reloadView()
|
||||
}
|
||||
|
||||
fun onPickDate() {
|
||||
view?.showDatePickerDialog(currentDate)
|
||||
}
|
||||
|
||||
fun onDateSet(year: Int, month: Int, day: Int) {
|
||||
loadData(LocalDate.of(year, month, day))
|
||||
reloadView()
|
||||
}
|
||||
|
||||
fun onSwipeRefresh() {
|
||||
Timber.i("Force refreshing the additional lessons")
|
||||
loadData(currentDate, true)
|
||||
}
|
||||
|
||||
fun onRetry() {
|
||||
view?.run {
|
||||
showErrorView(false)
|
||||
showProgress(true)
|
||||
}
|
||||
loadData(currentDate, true)
|
||||
}
|
||||
|
||||
private fun setBaseDateOnHolidays() {
|
||||
flow {
|
||||
val student = studentRepository.getCurrentStudent()
|
||||
emit(semesterRepository.getCurrentSemester(student))
|
||||
}.catch {
|
||||
Timber.i("Loading semester result: An exception occurred")
|
||||
}.onEach {
|
||||
baseDate = baseDate.getLastSchoolDayIfHoliday(it.schoolYear)
|
||||
currentDate = baseDate
|
||||
reloadNavigation()
|
||||
}.launch("holidays")
|
||||
}
|
||||
|
||||
private fun loadData(date: LocalDate, forceRefresh: Boolean = false) {
|
||||
currentDate = date
|
||||
|
||||
flowWithResourceIn {
|
||||
val student = studentRepository.getCurrentStudent()
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
timetableRepository.getTimetable(student, semester, date, date, forceRefresh, true)
|
||||
}.onEach {
|
||||
when (it.status) {
|
||||
Status.LOADING -> Timber.i("Loading additional lessons data started")
|
||||
Status.SUCCESS -> {
|
||||
Timber.i("Loading additional lessons lessons result: Success")
|
||||
view?.apply {
|
||||
updateData(it.data!!.second.sortedBy { item -> item.date })
|
||||
showEmpty(it.data.second.isEmpty())
|
||||
showErrorView(false)
|
||||
showContent(it.data.second.isNotEmpty())
|
||||
}
|
||||
analytics.logEvent(
|
||||
"load_data",
|
||||
"type" to "additional_lessons",
|
||||
"items" to it.data!!.second.size
|
||||
)
|
||||
}
|
||||
Status.ERROR -> {
|
||||
Timber.i("Loading additional lessons result: An exception occurred")
|
||||
errorHandler.dispatch(it.error!!)
|
||||
}
|
||||
}
|
||||
}.afterLoading {
|
||||
view?.run {
|
||||
hideRefresh()
|
||||
showProgress(false)
|
||||
enableSwipe(true)
|
||||
}
|
||||
}.launch()
|
||||
}
|
||||
|
||||
private fun showErrorViewOnError(message: String, error: Throwable) {
|
||||
view?.run {
|
||||
if (isViewEmpty) {
|
||||
lastError = error
|
||||
setErrorDetails(message)
|
||||
showErrorView(true)
|
||||
showEmpty(false)
|
||||
} else showError(message, error)
|
||||
}
|
||||
}
|
||||
|
||||
private fun reloadView() {
|
||||
Timber.i("Reload additional lessons view with the date ${currentDate.toFormattedString()}")
|
||||
view?.apply {
|
||||
showProgress(true)
|
||||
enableSwipe(false)
|
||||
showContent(false)
|
||||
showEmpty(false)
|
||||
showErrorView(false)
|
||||
clearData()
|
||||
reloadNavigation()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
private fun reloadNavigation() {
|
||||
view?.apply {
|
||||
showPreButton(!currentDate.minusDays(1).isHolidays)
|
||||
showNextButton(!currentDate.plusDays(1).isHolidays)
|
||||
updateNavigationDay(currentDate.toFormattedString("EEEE, dd.MM").capitalize())
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package io.github.wulkanowy.ui.modules.timetable.additional
|
||||
|
||||
import io.github.wulkanowy.data.db.entities.TimetableAdditional
|
||||
import io.github.wulkanowy.ui.base.BaseView
|
||||
import java.time.LocalDate
|
||||
|
||||
interface AdditionalLessonsView : BaseView {
|
||||
|
||||
val isViewEmpty: Boolean
|
||||
|
||||
fun initView()
|
||||
|
||||
fun updateData(data: List<TimetableAdditional>)
|
||||
|
||||
fun clearData()
|
||||
|
||||
fun updateNavigationDay(date: String)
|
||||
|
||||
fun hideRefresh()
|
||||
|
||||
fun showEmpty(show: Boolean)
|
||||
|
||||
fun showErrorView(show: Boolean)
|
||||
|
||||
fun setErrorDetails(message: String)
|
||||
|
||||
fun showProgress(show: Boolean)
|
||||
|
||||
fun enableSwipe(enable: Boolean)
|
||||
|
||||
fun showContent(show: Boolean)
|
||||
|
||||
fun showPreButton(show: Boolean)
|
||||
|
||||
fun showNextButton(show: Boolean)
|
||||
|
||||
fun showDatePickerDialog(currentDate: LocalDate)
|
||||
}
|
@ -107,7 +107,7 @@ class TimetableWidgetFactory(
|
||||
|
||||
val semester = semesterRepository.getCurrentSemester(student)
|
||||
timetableRepository.getTimetable(student, semester, date, date, false)
|
||||
.toFirstResult().data.orEmpty()
|
||||
.toFirstResult().data?.first.orEmpty()
|
||||
.sortedWith(compareBy({ it.number }, { !it.isStudentPlan }))
|
||||
.filter { if (prefRepository.showWholeClassPlan == "no") it.isStudentPlan else true }
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M19,19V8H5V19H19M16,1H18V3H19C20.11,3 21,3.9 21,5V19C21,20.11 20.11,21 19,21H5C3.89,21 3,20.1 3,19V5C3,3.89 3.89,3 5,3H6V1H8V3H16V1M11,9.5H13V12.5H16V14.5H13V17.5H11V14.5H8V12.5H11V9.5Z" />
|
||||
</vector>
|
163
app/src/main/res/layout/fragment_timetable_additional.xml
Normal file
163
app/src/main/res/layout/fragment_timetable_additional.xml
Normal file
@ -0,0 +1,163 @@
|
||||
<FrameLayout 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"
|
||||
tools:context=".ui.modules.timetable.additional.AdditionalLessonsFragment">
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="50dp">
|
||||
|
||||
<me.zhanghai.android.materialprogressbar.MaterialProgressBar
|
||||
android:id="@+id/additionalLessonsProgress"
|
||||
style="@style/Widget.MaterialProgressBar.ProgressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true" />
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/additionalLessonsSwipe"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/additionalLessonsRecycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:listitem="@layout/item_timetable_additional" />
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/additionalLessonsEmpty"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="10dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/additionalLessonsInfoImage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="100dp"
|
||||
android:minHeight="100dp"
|
||||
app:srcCompat="@drawable/ic_menu_timetable_lessons_additional"
|
||||
app:tint="?android:attr/textColorPrimary"
|
||||
tools:ignore="contentDescription" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/additionalLessonsInfo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/additional_lessons_no_items"
|
||||
android:textSize="20sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/additionalLessonsError"
|
||||
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/additionalLessonsErrorMessage"
|
||||
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/additionalLessonsErrorDetails"
|
||||
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/additionalLessonsErrorRetry"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/all_retry" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<io.github.wulkanowy.ui.widgets.MaterialLinearLayout
|
||||
android:id="@+id/additionalLessonsNavContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
tools:ignore="UnusedAttribute">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/additionalLessonsPreviousButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/all_prev"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingRight="12dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:scaleType="fitStart"
|
||||
android:tint="?colorPrimary"
|
||||
app:srcCompat="@drawable/ic_chevron_left" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/additionalLessonsNavDate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:fontFamily="sans-serif"
|
||||
android:gravity="center"
|
||||
android:textSize="16sp"
|
||||
tools:text="@tools:sample/date/ddmmyy" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/additionalLessonsNextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/all_next"
|
||||
android:paddingLeft="12dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingRight="12dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:scaleType="fitEnd"
|
||||
android:tint="?colorPrimary"
|
||||
app:srcCompat="@drawable/ic_chevron_right" />
|
||||
</io.github.wulkanowy.ui.widgets.MaterialLinearLayout>
|
||||
</FrameLayout>
|
41
app/src/main/res/layout/item_timetable_additional.xml
Normal file
41
app/src/main/res/layout/item_timetable_additional.xml
Normal file
@ -0,0 +1,41 @@
|
||||
<androidx.constraintlayout.widget.ConstraintLayout 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="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="6dp"
|
||||
tools:context=".ui.modules.timetable.additional.AdditionalLessonsAdapter">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/additionalLessonItemSubject"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@tools:sample/lorem" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/additionalLessonItemTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:maxLines="1"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:textSize="13sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/additionalLessonItemSubject"
|
||||
tools:text="11:11 - 12:12" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,6 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/timetableMenuAdditionalLessons"
|
||||
android:icon="@drawable/ic_menu_timetable_lessons_additional"
|
||||
android:orderInCategory="1"
|
||||
android:title="@string/additional_lessons_button"
|
||||
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/timetableMenuCompletedLessons"
|
||||
android:icon="@drawable/ic_menu_timetable_lessons_completed"
|
||||
|
@ -161,6 +161,12 @@
|
||||
<string name="completed_lessons_resources">Resources</string>
|
||||
|
||||
|
||||
<!--Additional lessons-->
|
||||
<string name="additional_lessons_title">Additional lessons</string>
|
||||
<string name="additional_lessons_button">Show additional lessons</string>
|
||||
<string name="additional_lessons_no_items">No info about additional lessons</string>
|
||||
|
||||
|
||||
<!--Attendance-->
|
||||
<string name="attendance_summary_button">Attendance summary</string>
|
||||
<string name="attendance_absence_school">Absent for school reasons</string>
|
||||
|
@ -56,7 +56,7 @@ class TimetableRemoteTest {
|
||||
of(2018, 9, 15)
|
||||
)
|
||||
}
|
||||
assertEquals(2, timetable.size)
|
||||
assertEquals(2, timetable.first.size)
|
||||
}
|
||||
|
||||
private fun getTimetable(date: LocalDate): Timetable {
|
||||
|
Loading…
x
Reference in New Issue
Block a user