From 27f6fc7e04908c10f04a611c1f68761e6ef4e11c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Sun, 14 Oct 2018 22:16:58 +0200 Subject: [PATCH] Modules improvements (#164) --- .circleci/config.yml | 2 +- app/build.gradle | 2 + .../repositories/local/AttendanceLocalTest.kt | 6 +- .../data/repositories/local/ExamLocalTest.kt | 6 +- .../repositories/local/TimetableLocalTest.kt | 16 +- .../wulkanowy/utils/security/ScramblerTest.kt | 10 +- .../wulkanowy/data/db/dao/GradeSummaryDao.kt | 4 + .../wulkanowy/data/db/dao/SemesterDao.kt | 3 +- .../wulkanowy/data/db/dao/StudentDao.kt | 3 +- .../wulkanowy/data/db/entities/Attendance.kt | 9 +- .../github/wulkanowy/data/db/entities/Exam.kt | 9 +- .../data/db/entities/GradeSummary.kt | 13 +- .../wulkanowy/data/db/entities/Semester.kt | 4 +- .../wulkanowy/data/db/entities/Student.kt | 4 +- .../wulkanowy/data/db/entities/Timetable.kt | 23 +- .../data/repositories/AttendanceRepository.kt | 42 ++-- .../data/repositories/ExamRepository.kt | 40 ++-- .../repositories/GradeSummaryRepository.kt | 9 +- .../data/repositories/TimetableRepository.kt | 43 ++-- .../repositories/local/GradeSummaryLocal.kt | 4 + .../data/repositories/local/SessionLocal.kt | 4 +- .../data/repositories/local/TimetableLocal.kt | 10 +- .../data/repositories/remote/GradeRemote.kt | 2 +- .../repositories/remote/TimetableRemote.kt | 2 +- .../github/wulkanowy/ui/base/BaseFragment.kt | 8 +- .../github/wulkanowy/ui/base/BasePresenter.kt | 7 +- .../wulkanowy/ui/login/LoginActivity.kt | 5 +- .../wulkanowy/ui/login/LoginPresenter.kt | 4 +- .../ui/login/form/LoginFormFragment.kt | 4 +- .../ui/login/form/LoginFormPresenter.kt | 4 +- .../ui/login/options/LoginOptionsFragment.kt | 4 +- .../ui/login/options/LoginOptionsPresenter.kt | 4 +- .../github/wulkanowy/ui/main/MainActivity.kt | 8 +- .../github/wulkanowy/ui/main/MainPresenter.kt | 5 +- .../io/github/wulkanowy/ui/main/MainView.kt | 2 - .../ui/main/attendance/AttendanceDialog.kt | 3 +- .../ui/main/attendance/AttendanceFragment.kt | 41 ++-- .../ui/main/attendance/AttendanceItem.kt | 28 ++- .../ui/main/attendance/AttendancePresenter.kt | 125 +++++------ .../ui/main/attendance/AttendanceView.kt | 8 +- .../wulkanowy/ui/main/exam/ExamDialog.kt | 3 +- .../wulkanowy/ui/main/exam/ExamFragment.kt | 35 +-- .../wulkanowy/ui/main/exam/ExamHeader.kt | 24 +- .../github/wulkanowy/ui/main/exam/ExamItem.kt | 30 +-- .../wulkanowy/ui/main/exam/ExamPresenter.kt | 136 ++++++------ .../github/wulkanowy/ui/main/exam/ExamView.kt | 12 +- .../wulkanowy/ui/main/grade/GradeFragment.kt | 11 +- .../wulkanowy/ui/main/grade/GradePresenter.kt | 26 ++- .../main/grade/details/GradeDetailsDialog.kt | 11 +- .../grade/details/GradeDetailsFragment.kt | 11 +- .../ui/main/grade/details/GradeDetailsItem.kt | 2 +- .../grade/details/GradeDetailsPresenter.kt | 8 +- .../grade/summary/GradeSummaryFragment.kt | 9 +- .../grade/summary/GradeSummaryPresenter.kt | 12 +- .../ui/main/timetable/TimetableDialog.kt | 3 +- .../ui/main/timetable/TimetableFragment.kt | 37 ++-- .../ui/main/timetable/TimetableItem.kt | 37 ++-- .../ui/main/timetable/TimetablePresenter.kt | 120 +++++----- .../ui/main/timetable/TimetableView.kt | 11 +- .../wulkanowy/ui/splash/SplashActivity.kt | 12 +- .../wulkanowy/ui/splash/SplashPresenter.kt | 4 +- .../github/wulkanowy/utils/GradeExtension.kt | 3 +- .../github/wulkanowy/utils/TimeExtension.kt | 50 ++--- .../wulkanowy/utils/security/Scrambler.kt | 209 ++++++++---------- app/src/main/res/layout/dialog_attendance.xml | 15 +- app/src/main/res/layout/dialog_exam.xml | 17 +- app/src/main/res/layout/dialog_timetable.xml | 14 +- .../main/res/layout/fragment_attendance.xml | 28 ++- .../res/layout/fragment_attendance_tab.xml | 63 ------ app/src/main/res/layout/fragment_exam.xml | 28 ++- app/src/main/res/layout/fragment_grade.xml | 10 +- .../main/res/layout/fragment_timetable.xml | 28 ++- app/src/main/res/layout/header_attendance.xml | 86 ------- app/src/main/res/layout/header_exam.xml | 20 +- .../main/res/layout/header_grade_summary.xml | 18 +- app/src/main/res/layout/header_timetable.xml | 71 ------ app/src/main/res/layout/item_attendance.xml | 1 + app/src/main/res/layout/item_exam.xml | 19 +- .../main/res/layout/item_grade_summary.xml | 26 +-- app/src/main/res/layout/item_timetable.xml | 27 +-- app/src/main/res/values-pl/strings.xml | 6 +- app/src/main/res/values/strings.xml | 7 +- .../remote/TimetableRemoteTest.kt | 3 +- .../wulkanowy/ui/login/LoginPresenterTest.kt | 2 +- .../ui/login/form/LoginFormPresenterTest.kt | 2 +- .../options/LoginOptionsPresenterTest.kt | 2 +- .../wulkanowy/ui/main/MainPresenterTest.kt | 2 +- .../ui/splash/SplashPresenterTest.kt | 4 +- .../wulkanowy/utils/GradeExtensionTest.kt | 12 +- .../wulkanowy/utils/TimeExtensionTest.kt | 93 ++++---- 90 files changed, 881 insertions(+), 1069 deletions(-) delete mode 100644 app/src/main/res/layout/fragment_attendance_tab.xml delete mode 100644 app/src/main/res/layout/header_attendance.xml delete mode 100644 app/src/main/res/layout/header_timetable.xml diff --git a/.circleci/config.yml b/.circleci/config.yml index 43e77fb12..f1f6a4c15 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ references: container_config: &container_config docker: - - image: circleci/android:api-27-alpha + - image: circleci/android:api-28-alpha working_directory: *workspace_root environment: environment: diff --git a/app/build.gradle b/app/build.gradle index b6cc65c4d..535034880 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -73,6 +73,7 @@ ext.supportVersion = "28.0.0" dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation('com.github.wulkanowy:api:a5667f8000') { exclude module: "threetenbp" } + implementation "com.android.support:support-v4:$supportVersion" implementation "com.android.support:appcompat-v7:$supportVersion" implementation "com.android.support:design:$supportVersion" @@ -116,6 +117,7 @@ dependencies { testImplementation "junit:junit:4.12" testImplementation "io.mockk:mockk:1.8.8" testImplementation "org.mockito:mockito-inline:2.21.0" + testImplementation 'org.threeten:threetenbp:1.3.7' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation "org.mockito:mockito-android:2.21.0" diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/AttendanceLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/AttendanceLocalTest.kt index 3af246eb8..01e2ad396 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/AttendanceLocalTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/AttendanceLocalTest.kt @@ -34,9 +34,9 @@ class AttendanceLocalTest { @Test fun saveAndReadTest() { attendanceLocal.saveAttendance(listOf( - Attendance(0, "1", "2", LocalDate.of(2018, 9, 10), 0, "", ""), - Attendance(0, "1", "2", LocalDate.of(2018, 9, 14), 0, "", ""), - Attendance(0, "1", "2", LocalDate.of(2018, 9, 17), 0, "", "") + Attendance("1", "2", LocalDate.of(2018, 9, 10), 0, "", ""), + Attendance("1", "2", LocalDate.of(2018, 9, 14), 0, "", ""), + Attendance("1", "2", LocalDate.of(2018, 9, 17), 0, "", "") )) val attendance = attendanceLocal diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/ExamLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/ExamLocalTest.kt index 8a6b4bcc9..0470754d0 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/ExamLocalTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/ExamLocalTest.kt @@ -34,9 +34,9 @@ class ExamLocalTest { @Test fun saveAndReadTest() { examLocal.saveExams(listOf( - Exam(0, "1", "2", LocalDate.of(2018, 9, 10), LocalDate.now(), "", "", "", "", "", ""), - Exam(0, "1", "2", LocalDate.of(2018, 9, 14), LocalDate.now(), "", "", "", "", "", ""), - Exam(0, "1", "2", LocalDate.of(2018, 9, 17), LocalDate.now(), "", "", "", "", "", "") + Exam("1", "2", LocalDate.of(2018, 9, 10), LocalDate.now(), "", "", "", "", "", ""), + Exam("1", "2", LocalDate.of(2018, 9, 14), LocalDate.now(), "", "", "", "", "", ""), + Exam("1", "2", LocalDate.of(2018, 9, 17), LocalDate.now(), "", "", "", "", "", "") )) val exams = examLocal diff --git a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/TimetableLocalTest.kt b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/TimetableLocalTest.kt index 64531239b..5e176519f 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/TimetableLocalTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/data/repositories/local/TimetableLocalTest.kt @@ -11,6 +11,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.threeten.bp.LocalDate +import org.threeten.bp.LocalDateTime import kotlin.test.assertEquals @RunWith(AndroidJUnit4::class) @@ -33,14 +34,17 @@ class TimetableLocalTest { @Test fun saveAndReadTest() { - timetableDb.saveLessons(listOf( - Timetable(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 10)), - Timetable(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 14)), - Timetable(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 17)) // in next week + timetableDb.saveTimetable(listOf( + Timetable("1", "2", 1, LocalDateTime.now(), LocalDateTime.now(), + LocalDate.of(2018, 9, 10), "", "", "", "", ""), + Timetable("1", "2", 1, LocalDateTime.now(), LocalDateTime.now(), + LocalDate.of(2018, 9, 14), "", "", "", "", ""), + Timetable("1", "2", 1, LocalDateTime.now(), LocalDateTime.now(), + LocalDate.of(2018, 9, 17), "", "", "", "", "") )) - val exams = timetableDb.getLessons( - Semester(studentId = "1", diaryId = "2", semesterId = "3", diaryName = "", semesterName = 1), + val exams = timetableDb.getTimetable( + Semester(0, "1", "2", "3", "", 1), LocalDate.of(2018, 9, 10), LocalDate.of(2018, 9, 14) ).blockingGet() diff --git a/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.kt b/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.kt index 162008de2..c6bcb9b70 100644 --- a/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.kt +++ b/app/src/androidTest/java/io/github/wulkanowy/utils/security/ScramblerTest.kt @@ -17,32 +17,32 @@ class ScramblerTest { @Test fun encryptDecryptTest() { - assertEquals("TEST", Scrambler.decrypt(Scrambler.encrypt("TEST", + assertEquals("TEST", decrypt(encrypt("TEST", InstrumentationRegistry.getTargetContext()))) } @Test fun emptyTextEncryptTest() { assertFailsWith { - Scrambler.decrypt("") + decrypt("") } assertFailsWith { - Scrambler.encrypt("", InstrumentationRegistry.getTargetContext()) + encrypt("", InstrumentationRegistry.getTargetContext()) } } @Test @SdkSuppress(minSdkVersion = 18) fun emptyKeyStoreTest() { - val text = Scrambler.encrypt("test", InstrumentationRegistry.getTargetContext()) + val text = encrypt("test", InstrumentationRegistry.getTargetContext()) val keyStore = KeyStore.getInstance("AndroidKeyStore") keyStore.load(null) keyStore.deleteEntry("USER_PASSWORD") assertFailsWith { - Scrambler.decrypt(text) + decrypt(text) } } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSummaryDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSummaryDao.kt index 6dab2690d..6670b5650 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSummaryDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeSummaryDao.kt @@ -1,6 +1,7 @@ package io.github.wulkanowy.data.db.dao import android.arch.persistence.room.Dao +import android.arch.persistence.room.Delete import android.arch.persistence.room.Insert import android.arch.persistence.room.OnConflictStrategy.REPLACE import android.arch.persistence.room.Query @@ -13,6 +14,9 @@ interface GradeSummaryDao { @Insert(onConflict = REPLACE) fun insertAll(gradesSummary: List) + @Delete + fun deleteAll(gradesSummary: List) + @Query("SELECT * FROM grades_summary WHERE student_id = :studentId AND semester_id = :semesterId") fun getGradesSummary(semesterId: String, studentId: String): Maybe> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt index 363da09b4..8cbc12d03 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/SemesterDao.kt @@ -2,7 +2,6 @@ package io.github.wulkanowy.data.db.dao import android.arch.persistence.room.Dao import android.arch.persistence.room.Insert -import android.arch.persistence.room.OnConflictStrategy.REPLACE import android.arch.persistence.room.Query import io.github.wulkanowy.data.db.entities.Semester import io.reactivex.Single @@ -10,7 +9,7 @@ import io.reactivex.Single @Dao interface SemesterDao { - @Insert(onConflict = REPLACE) + @Insert fun insertAll(semester: List) @Query("SELECT * FROM Semesters WHERE student_id = :studentId") diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt index 08ca48d23..39292bad8 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/StudentDao.kt @@ -2,7 +2,6 @@ package io.github.wulkanowy.data.db.dao import android.arch.persistence.room.Dao import android.arch.persistence.room.Insert -import android.arch.persistence.room.OnConflictStrategy.REPLACE import android.arch.persistence.room.Query import io.github.wulkanowy.data.db.entities.Student import io.reactivex.Maybe @@ -10,7 +9,7 @@ import io.reactivex.Maybe @Dao interface StudentDao { - @Insert(onConflict = REPLACE) + @Insert fun insert(student: Student): Long @Query("SELECT * FROM Students WHERE id = :id") diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Attendance.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Attendance.kt index be8911a57..a52f09d66 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Attendance.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Attendance.kt @@ -9,9 +9,6 @@ import java.io.Serializable @Entity(tableName = "Attendance") data class Attendance( - @PrimaryKey(autoGenerate = true) - var id: Long = 0, - @ColumnInfo(name = "student_id") var studentId: String, @@ -37,4 +34,8 @@ data class Attendance( var excused: Boolean = false, var deleted: Boolean = false -) : Serializable +) : Serializable { + + @PrimaryKey(autoGenerate = true) + var id: Long = 0 +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Exam.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Exam.kt index 807c8f393..d54f56607 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Exam.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Exam.kt @@ -9,9 +9,6 @@ import java.io.Serializable @Entity(tableName = "Exams") data class Exam( - @PrimaryKey(autoGenerate = true) - var id: Long = 0, - @ColumnInfo(name = "student_id") var studentId: String, @@ -35,4 +32,8 @@ data class Exam( @ColumnInfo(name = "teacher_symbol") var teacherSymbol: String -) : Serializable +) : Serializable { + + @PrimaryKey(autoGenerate = true) + var id: Long = 0 +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt index 58528a04e..6996ed3ce 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/GradeSummary.kt @@ -2,16 +2,11 @@ package io.github.wulkanowy.data.db.entities import android.arch.persistence.room.ColumnInfo import android.arch.persistence.room.Entity -import android.arch.persistence.room.Index import android.arch.persistence.room.PrimaryKey -@Entity(tableName = "Grades_Summary", - indices = [Index(value = ["semester_id", "student_id", "subject"], unique = true)]) +@Entity(tableName = "Grades_Summary") data class GradeSummary( - @PrimaryKey(autoGenerate = true) - var id: Long = 0, - @ColumnInfo(name = "semester_id") var semesterId: String, @@ -23,4 +18,8 @@ data class GradeSummary( var predictedGrade: String, var finalGrade: String -) +) { + @PrimaryKey(autoGenerate = true) + var id: Long = 0 + +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Semester.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Semester.kt index 2bad1cba1..82c52b77c 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Semester.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Semester.kt @@ -2,11 +2,9 @@ package io.github.wulkanowy.data.db.entities import android.arch.persistence.room.ColumnInfo import android.arch.persistence.room.Entity -import android.arch.persistence.room.Index import android.arch.persistence.room.PrimaryKey -@Entity(tableName = "Semesters", - indices = [Index(value = ["semester_id", "diary_id", "student_id"], unique = true)]) +@Entity(tableName = "Semesters") data class Semester( @PrimaryKey(autoGenerate = true) diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Student.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Student.kt index 05c6c6885..30186610c 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Student.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Student.kt @@ -2,11 +2,9 @@ package io.github.wulkanowy.data.db.entities import android.arch.persistence.room.ColumnInfo import android.arch.persistence.room.Entity -import android.arch.persistence.room.Index import android.arch.persistence.room.PrimaryKey -@Entity(tableName = "Students", - indices = [Index(value = ["student_id", "student_name"], unique = true)]) +@Entity(tableName = "Students") data class Student( @PrimaryKey(autoGenerate = true) diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Timetable.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Timetable.kt index b06f070ff..2a3ba2e14 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/entities/Timetable.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Timetable.kt @@ -10,14 +10,11 @@ import java.io.Serializable @Entity(tableName = "Timetable") data class Timetable( - @PrimaryKey(autoGenerate = true) - var id: Long = 0, - @ColumnInfo(name = "student_id") - var studentId: String = "", + var studentId: String, @ColumnInfo(name = "diary_id") - var diaryId: String = "", + var diaryId: String, val number: Int = 0, @@ -27,17 +24,21 @@ data class Timetable( val date: LocalDate, - val subject: String = "", + val subject: String, - val group: String = "", + val group: String, - val room: String = "", + val room: String, - val teacher: String = "", + val teacher: String, - val info: String = "", + val info: String, val changes: Boolean = false, val canceled: Boolean = false -) : Serializable +) : Serializable { + + @PrimaryKey(autoGenerate = true) + var id: Long = 0 +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt index ddbc4d069..ce62cafa3 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceRepository.kt @@ -6,11 +6,10 @@ import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.repositories.local.AttendanceLocal import io.github.wulkanowy.data.repositories.remote.AttendanceRemote -import io.github.wulkanowy.utils.weekFirstDayAlwaysCurrent +import io.github.wulkanowy.utils.friday +import io.github.wulkanowy.utils.monday import io.reactivex.Single -import org.threeten.bp.DayOfWeek import org.threeten.bp.LocalDate -import org.threeten.bp.temporal.TemporalAdjusters import java.net.UnknownHostException import javax.inject.Inject import javax.inject.Singleton @@ -22,24 +21,25 @@ class AttendanceRepository @Inject constructor( private val remote: AttendanceRemote ) { - fun getAttendance(semester: Semester, startDate: LocalDate, endDate: LocalDate, forceRefresh: Boolean = false): Single> { - val start = startDate.weekFirstDayAlwaysCurrent - val end = endDate.with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY)) - - return local.getAttendance(semester, start, end).filter { !forceRefresh } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings).flatMap { - if (it) remote.getAttendance(semester, start, end) - else Single.error(UnknownHostException()) - }.flatMap { newLessons -> - local.getAttendance(semester, start, end).toSingle(emptyList()).map { grades -> - local.deleteAttendance(grades - newLessons) - local.saveAttendance(newLessons - grades) - newLessons - } - }).map { list -> - list.asSequence().filter { - it.date in startDate..endDate - }.toList() + fun getAttendance(semester: Semester, startDate: LocalDate, endDate: LocalDate, forceRefresh: Boolean) + : Single> { + return Single.fromCallable { startDate.monday to endDate.friday } + .flatMap { dates -> + local.getAttendance(semester, dates.first, dates.second).filter { !forceRefresh } + .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings).flatMap { + if (it) remote.getAttendance(semester, dates.first, dates.second) + else Single.error(UnknownHostException()) + }.flatMap { newAttendance -> + local.getAttendance(semester, dates.first, dates.second) + .toSingle(emptyList()) + .doOnSuccess { oldAttendance -> + local.deleteAttendance(oldAttendance - newAttendance) + local.saveAttendance(newAttendance - oldAttendance) + } + }.flatMap { + local.getAttendance(semester, dates.first, dates.second) + .toSingle(emptyList()) + }).map { list -> list.filter { it.date in startDate..endDate } } } } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt index a334719ec..d956d9139 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/ExamRepository.kt @@ -6,11 +6,10 @@ import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.repositories.local.ExamLocal import io.github.wulkanowy.data.repositories.remote.ExamRemote -import io.github.wulkanowy.utils.weekFirstDayAlwaysCurrent +import io.github.wulkanowy.utils.friday +import io.github.wulkanowy.utils.monday import io.reactivex.Single -import org.threeten.bp.DayOfWeek import org.threeten.bp.LocalDate -import org.threeten.bp.temporal.TemporalAdjusters import java.net.UnknownHostException import javax.inject.Inject import javax.inject.Singleton @@ -23,23 +22,24 @@ class ExamRepository @Inject constructor( ) { fun getExams(semester: Semester, startDate: LocalDate, endDate: LocalDate, forceRefresh: Boolean = false): Single> { - val start = startDate.weekFirstDayAlwaysCurrent - val end = endDate.with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY)) - - return local.getExams(semester, start, end).filter { !forceRefresh } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings).flatMap { - if (it) remote.getExams(semester, start, end) - else Single.error(UnknownHostException()) - }.flatMap { newExams -> - local.getExams(semester, start, end).toSingle(emptyList()).map { grades -> - local.deleteExams(grades - newExams) - local.saveExams(newExams - grades) - newExams - } - }).map { list -> - list.asSequence().filter { - it.date in startDate..endDate - }.toList() + return Single.fromCallable { startDate.monday to endDate.friday } + .flatMap { dates -> + local.getExams(semester, dates.first, dates.second).filter { !forceRefresh } + .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) + .flatMap { + if (it) remote.getExams(semester, dates.first, dates.second) + else Single.error(UnknownHostException()) + }.flatMap { newExams -> + local.getExams(semester, dates.first, dates.second) + .toSingle(emptyList()) + .doOnSuccess { oldExams -> + local.deleteExams(oldExams - newExams) + local.saveExams(newExams - oldExams) + } + }.flatMap { + local.getExams(semester, dates.first, dates.second) + .toSingle(emptyList()) + }).map { list -> list.filter { it.date in startDate..endDate } } } } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeSummaryRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeSummaryRepository.kt index 58afe3ecb..e809ff59d 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/GradeSummaryRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/GradeSummaryRepository.kt @@ -24,7 +24,12 @@ class GradeSummaryRepository @Inject constructor( .flatMap { if (it) remote.getGradeSummary(semester) else Single.error(UnknownHostException()) - } - ).doOnSuccess { local.saveGradesSummary(it) } + }.flatMap { newGradesSummary -> + local.getGradesSummary(semester).toSingle(emptyList()) + .doOnSuccess { oldGradesSummary -> + local.deleteGradesSummary(oldGradesSummary - newGradesSummary) + local.saveGradesSummary(newGradesSummary - oldGradesSummary) + } + }.flatMap { local.getGradesSummary(semester).toSingle(emptyList()) }) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt index 728e67a3d..95a1563fd 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/TimetableRepository.kt @@ -6,11 +6,10 @@ import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.repositories.local.TimetableLocal import io.github.wulkanowy.data.repositories.remote.TimetableRemote -import io.github.wulkanowy.utils.weekFirstDayAlwaysCurrent +import io.github.wulkanowy.utils.friday +import io.github.wulkanowy.utils.monday import io.reactivex.Single -import org.threeten.bp.DayOfWeek import org.threeten.bp.LocalDate -import org.threeten.bp.temporal.TemporalAdjusters import java.net.UnknownHostException import javax.inject.Inject import javax.inject.Singleton @@ -22,24 +21,26 @@ class TimetableRepository @Inject constructor( private val remote: TimetableRemote ) { - fun getTimetable(semester: Semester, startDate: LocalDate, endDate: LocalDate, forceRefresh: Boolean = false): Single> { - val start = startDate.weekFirstDayAlwaysCurrent - val end = endDate.with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY)) - - return local.getLessons(semester, start, end).filter { !forceRefresh } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings).flatMap { - if (it) remote.getLessons(semester, start, end) - else Single.error(UnknownHostException()) - }.flatMap { newLessons -> - local.getLessons(semester, start, end).toSingle(emptyList()).map { lessons -> - local.deleteLessons(lessons - newLessons) - local.saveLessons(newLessons - lessons) - newLessons - } - }).map { list -> - list.asSequence().filter { - it.date in startDate..endDate - }.toList() + fun getTimetable(semester: Semester, startDate: LocalDate, endDate: LocalDate, forceRefresh: Boolean = false) + : Single> { + return Single.fromCallable { startDate.monday to endDate.friday } + .flatMap { dates -> + local.getTimetable(semester, dates.first, dates.second).filter { !forceRefresh } + .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) + .flatMap { + if (it) remote.getTimetable(semester, dates.first, dates.second) + else Single.error(UnknownHostException()) + }.flatMap { newTimetable -> + local.getTimetable(semester, dates.first, dates.second) + .toSingle(emptyList()) + .doOnSuccess { oldTimetable -> + local.deleteTimetable(oldTimetable - newTimetable) + local.saveTimetable(newTimetable - oldTimetable) + } + }.flatMap { + local.getTimetable(semester, dates.first, dates.second) + .toSingle(emptyList()) + }).map { list -> list.filter { it.date in startDate..endDate } } } } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/local/GradeSummaryLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/local/GradeSummaryLocal.kt index d06808b15..3b7c2d7b1 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/local/GradeSummaryLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/local/GradeSummaryLocal.kt @@ -18,4 +18,8 @@ class GradeSummaryLocal @Inject constructor(private val gradeSummaryDb: GradeSum fun saveGradesSummary(gradesSummary: List) { gradeSummaryDb.insertAll(gradesSummary) } + + fun deleteGradesSummary(gradesSummary: List) { + gradeSummaryDb.deleteAll(gradesSummary) + } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/local/SessionLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/local/SessionLocal.kt index e507899a6..b26ce6371 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/local/SessionLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/local/SessionLocal.kt @@ -6,8 +6,8 @@ import io.github.wulkanowy.data.db.dao.SemesterDao import io.github.wulkanowy.data.db.dao.StudentDao import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student -import io.github.wulkanowy.utils.security.Scrambler.decrypt -import io.github.wulkanowy.utils.security.Scrambler.encrypt +import io.github.wulkanowy.utils.security.decrypt +import io.github.wulkanowy.utils.security.encrypt import io.reactivex.Completable import io.reactivex.Maybe import io.reactivex.Single diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/local/TimetableLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/local/TimetableLocal.kt index 792cee8e3..de5329eb3 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/local/TimetableLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/local/TimetableLocal.kt @@ -9,16 +9,16 @@ import javax.inject.Inject class TimetableLocal @Inject constructor(private val timetableDb: TimetableDao) { - fun getLessons(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe> { + fun getTimetable(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe> { return timetableDb.getTimetable(semester.diaryId, semester.studentId, startDate, endDate) .filter { !it.isEmpty() } } - fun saveLessons(lessons: List) { - timetableDb.insertAll(lessons) + fun saveTimetable(timetables: List) { + timetableDb.insertAll(timetables) } - fun deleteLessons(exams: List) { - timetableDb.deleteAll(exams) + fun deleteTimetable(timetables: List) { + timetableDb.deleteAll(timetables) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/remote/GradeRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/remote/GradeRemote.kt index 2f4c9a0ec..e380777e7 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/remote/GradeRemote.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/remote/GradeRemote.kt @@ -26,7 +26,7 @@ class GradeRemote @Inject constructor(private val api: Api) { subject = it.subject, entry = it.entry, value = it.value, - modifier = it.modifier.toDouble(), + modifier = it.modifier, comment = it.comment, color = it.color, gradeSymbol = it.symbol, diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/remote/TimetableRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/remote/TimetableRemote.kt index 16daf19d7..643863957 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/remote/TimetableRemote.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/remote/TimetableRemote.kt @@ -11,7 +11,7 @@ import javax.inject.Inject class TimetableRemote @Inject constructor(private val api: Api) { - fun getLessons(semester: Semester, startDate: LocalDate, endDate: LocalDate): Single> { + fun getTimetable(semester: Semester, startDate: LocalDate, endDate: LocalDate): Single> { return Single.just(api.run { if (diaryId != semester.diaryId) { diaryId = semester.diaryId diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt index 8c6adb2cc..57279ccba 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BaseFragment.kt @@ -1,10 +1,16 @@ package io.github.wulkanowy.ui.base +import android.support.design.widget.Snackbar +import android.support.design.widget.Snackbar.LENGTH_LONG +import android.view.View import dagger.android.support.DaggerFragment abstract class BaseFragment : DaggerFragment(), BaseView { + protected var messageContainer: View? = null + override fun showMessage(text: String) { - (activity as BaseActivity?)?.showMessage(text) + if (messageContainer == null) (activity as? BaseActivity)?.showMessage(text) + else messageContainer?.also { Snackbar.make(it, text, LENGTH_LONG).show() } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt index 76c8533ee..55816e409 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/base/BasePresenter.kt @@ -9,15 +9,12 @@ open class BasePresenter(private val errorHandler: ErrorHandler) { var view: T? = null - val isViewAttached: Boolean - get() = view != null - - open fun attachView(view: T) { + open fun onAttachView(view: T) { this.view = view errorHandler.showErrorMessage = { view.showMessage(it) } } - open fun detachView() { + open fun onDetachView() { view = null disposable.clear() errorHandler.clear() diff --git a/app/src/main/java/io/github/wulkanowy/ui/login/LoginActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/login/LoginActivity.kt index 4fb1ee519..cb59e911a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/login/LoginActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/login/LoginActivity.kt @@ -27,8 +27,9 @@ class LoginActivity : BaseActivity(), LoginView, LoginSwitchListener { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_login) - presenter.attachView(this) messageContainer = loginContainer + + presenter.onAttachView(this) } override fun onBackPressed() { @@ -65,7 +66,7 @@ class LoginActivity : BaseActivity(), LoginView, LoginSwitchListener { override fun currentViewPosition() = loginViewpager.currentItem public override fun onDestroy() { - presenter.detachView() + presenter.onDetachView() super.onDestroy() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/login/LoginPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/login/LoginPresenter.kt index 51114be38..4d2ac1f75 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/login/LoginPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/login/LoginPresenter.kt @@ -7,8 +7,8 @@ import javax.inject.Inject class LoginPresenter @Inject constructor(errorHandler: ErrorHandler) : BasePresenter(errorHandler) { - override fun attachView(view: LoginView) { - super.attachView(view) + override fun onAttachView(view: LoginView) { + super.onAttachView(view) view.run { initAdapter() hideActionBar() diff --git a/app/src/main/java/io/github/wulkanowy/ui/login/form/LoginFormFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/login/form/LoginFormFragment.kt index 719a77bac..1f9091ccf 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/login/form/LoginFormFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/login/form/LoginFormFragment.kt @@ -31,7 +31,7 @@ class LoginFormFragment : BaseFragment(), LoginFormView { override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - presenter.attachView(this) + presenter.onAttachView(this) } override fun initInputs() { @@ -139,6 +139,6 @@ class LoginFormFragment : BaseFragment(), LoginFormView { override fun onDestroyView() { super.onDestroyView() - presenter.detachView() + presenter.onDetachView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/login/form/LoginFormPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/login/form/LoginFormPresenter.kt index d201b5bcc..1ecd783de 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/login/form/LoginFormPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/login/form/LoginFormPresenter.kt @@ -14,8 +14,8 @@ class LoginFormPresenter @Inject constructor( private var wasEmpty = false - override fun attachView(view: LoginFormView) { - super.attachView(view) + override fun onAttachView(view: LoginFormView) { + super.onAttachView(view) view.initInputs() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/login/options/LoginOptionsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/login/options/LoginOptionsFragment.kt index b318daac2..4f4567f22 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/login/options/LoginOptionsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/login/options/LoginOptionsFragment.kt @@ -35,7 +35,7 @@ class LoginOptionsFragment : BaseFragment(), LoginOptionsView { override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - presenter.attachView(this) + presenter.onAttachView(this) } override fun initRecycler() { @@ -80,6 +80,6 @@ class LoginOptionsFragment : BaseFragment(), LoginOptionsView { override fun onDestroyView() { super.onDestroyView() - presenter.detachView() + presenter.onDetachView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/login/options/LoginOptionsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/login/options/LoginOptionsPresenter.kt index 3fb3c13d4..bb38262ff 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/login/options/LoginOptionsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/login/options/LoginOptionsPresenter.kt @@ -13,8 +13,8 @@ class LoginOptionsPresenter @Inject constructor( private val schedulers: SchedulersManager) : BasePresenter(errorHandler) { - override fun attachView(view: LoginOptionsView) { - super.attachView(view) + override fun onAttachView(view: LoginOptionsView) { + super.onAttachView(view) view.initRecycler() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/MainActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/main/MainActivity.kt index 803b8729b..79c653da3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/MainActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/MainActivity.kt @@ -39,7 +39,7 @@ class MainActivity : BaseActivity(), MainView { setSupportActionBar(mainToolbar) messageContainer = mainFragmentContainer - presenter.attachView(this) + presenter.onAttachView(this) navController.initialize(DEFAULT_TAB, savedInstanceState) } @@ -90,10 +90,6 @@ class MainActivity : BaseActivity(), MainView { supportActionBar?.title = title } - override fun expandActionBar(show: Boolean) { - mainAppBarContainer.setExpanded(show, true) - } - override fun viewTitle(index: Int): String { return getString(listOf(R.string.grade_title, R.string.attendance_title, @@ -119,6 +115,6 @@ class MainActivity : BaseActivity(), MainView { override fun onDestroy() { super.onDestroy() - presenter.detachView() + presenter.onDetachView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/MainPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/main/MainPresenter.kt index 27dc9425d..1a94a30b8 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/MainPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/MainPresenter.kt @@ -7,8 +7,8 @@ import javax.inject.Inject class MainPresenter @Inject constructor(errorHandler: ErrorHandler) : BasePresenter(errorHandler) { - override fun attachView(view: MainView) { - super.attachView(view) + override fun onAttachView(view: MainView) { + super.onAttachView(view) view.initView() } @@ -22,7 +22,6 @@ class MainPresenter @Inject constructor(errorHandler: ErrorHandler) fun onTabSelected(index: Int, wasSelected: Boolean): Boolean { return view?.run { - expandActionBar(true) if (wasSelected) { notifyMenuViewReselected() false diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/MainView.kt b/app/src/main/java/io/github/wulkanowy/ui/main/MainView.kt index fee8630e7..67a0b6689 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/MainView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/MainView.kt @@ -10,8 +10,6 @@ interface MainView : BaseView { fun setViewTitle(title: String) - fun expandActionBar(show: Boolean) - fun viewTitle(index: Int): String fun currentMenuIndex(): Int diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceDialog.kt index 648acf3b9..75d39427a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceDialog.kt @@ -26,14 +26,13 @@ class AttendanceDialog : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(DialogFragment.STYLE_NORMAL, R.style.DialogFragmentTheme) + setStyle(STYLE_NO_TITLE, 0) arguments?.run { attendance = getSerializable(ARGUMENT_KEY) as Attendance } } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - dialog.setTitle(getString(R.string.all_details)) return inflater.inflate(R.layout.dialog_attendance, container, false) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceFragment.kt index 283ccda69..1b0cd6f8a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceFragment.kt @@ -10,11 +10,12 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.main.MainView import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.fragment_attendance.* import javax.inject.Inject -class AttendanceFragment : BaseFragment(), AttendanceView { +class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MenuFragmentView { @Inject lateinit var presenter: AttendancePresenter @@ -24,6 +25,7 @@ class AttendanceFragment : BaseFragment(), AttendanceView { companion object { private const val SAVED_DATE_KEY = "CURRENT_DATE" + fun newInstance() = AttendanceFragment() } @@ -33,41 +35,42 @@ class AttendanceFragment : BaseFragment(), AttendanceView { override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - presenter.run { - attachView(this@AttendanceFragment) - loadData(date = savedInstanceState?.getLong(SAVED_DATE_KEY)) - } + messageContainer = attendanceRecycler + presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY)) } override fun initView() { - attendanceAdapter.run { - isAutoCollapseOnExpand = true - isAutoScrollOnExpand = true - setOnItemClickListener { presenter.onAttendanceItemSelected(getItem(it))} + attendanceAdapter.apply { + setOnItemClickListener { presenter.onAttendanceItemSelected(getItem(it)) } } + attendanceRecycler.run { layoutManager = SmoothScrollLinearLayoutManager(context) adapter = attendanceAdapter } - attendanceSwipe.setOnRefreshListener { presenter.loadData(date = null, forceRefresh = true) } - attendancePreviousButton.setOnClickListener { presenter.loadAttendanceForPreviousDay() } - attendanceNextButton.setOnClickListener { presenter.loadAttendanceForNextDay() } + attendanceSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } + attendancePreviousButton.setOnClickListener { presenter.onPreviousDay() } + attendanceNextButton.setOnClickListener { presenter.onNextDay() } } override fun updateData(data: List) { attendanceAdapter.updateDataSet(data, true) } - override fun clearData() { - attendanceAdapter.clear() - } - override fun updateNavigationDay(date: String) { attendanceNavDate.text = date } + override fun clearData() { + attendanceAdapter.clear() + } + override fun isViewEmpty() = attendanceAdapter.isEmpty + override fun onFragmentReselected() { + presenter.onViewReselected() + } + override fun showEmpty(show: Boolean) { attendanceEmpty.visibility = if (show) View.VISIBLE else View.GONE } @@ -80,8 +83,8 @@ class AttendanceFragment : BaseFragment(), AttendanceView { attendanceRecycler.visibility = if (show) View.VISIBLE else View.GONE } - override fun showRefresh(show: Boolean) { - attendanceSwipe.isRefreshing = show + override fun hideRefresh() { + attendanceSwipe.isRefreshing = false } override fun showPreButton(show: Boolean) { @@ -102,8 +105,8 @@ class AttendanceFragment : BaseFragment(), AttendanceView { } override fun onDestroyView() { + presenter.onDetachView() super.onDestroyView() - presenter.detachView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceItem.kt b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceItem.kt index 2fedeca10..13e4d0dfe 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceItem.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceItem.kt @@ -1,6 +1,8 @@ package io.github.wulkanowy.ui.main.attendance import android.view.View +import android.view.View.INVISIBLE +import android.view.View.VISIBLE import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.IFlexible @@ -10,9 +12,7 @@ import io.github.wulkanowy.data.db.entities.Attendance import kotlinx.android.extensions.LayoutContainer import kotlinx.android.synthetic.main.item_attendance.* -class AttendanceItem : AbstractFlexibleItem() { - - lateinit var attendance: Attendance +class AttendanceItem(val attendance: Attendance) : AbstractFlexibleItem() { override fun createViewHolder(view: View, adapter: FlexibleAdapter>): ViewHolder { return ViewHolder(view, adapter) @@ -20,6 +20,16 @@ class AttendanceItem : AbstractFlexibleItem() { override fun getLayoutRes(): Int = R.layout.item_attendance + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, + position: Int, payloads: MutableList?) { + holder.apply { + attendanceItemNumber.text = attendance.number.toString() + attendanceItemSubject.text = attendance.subject + attendanceItemDescription.text = attendance.name + attendanceItemAlert.visibility = attendance.run { if (absence && !excused) VISIBLE else INVISIBLE } + } + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false @@ -34,22 +44,10 @@ class AttendanceItem : AbstractFlexibleItem() { return attendance.hashCode() } - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, - position: Int, payloads: MutableList?) { - holder.bind(attendance) - } - class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer { override val containerView: View get() = contentView - - fun bind(lesson: Attendance) { - attendanceItemNumber.text = lesson.number.toString() - attendanceItemSubject.text = lesson.subject - attendanceItemDescription.text = lesson.name - attendanceItemAlert.visibility = if (lesson.absence && !lesson.excused) View.VISIBLE else View.INVISIBLE - } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendancePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendancePresenter.kt index d852dcf13..a09c65c22 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendancePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendancePresenter.kt @@ -2,14 +2,15 @@ package io.github.wulkanowy.ui.main.attendance import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.data.ErrorHandler -import io.github.wulkanowy.data.db.entities.Attendance -import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.repositories.AttendanceRepository import io.github.wulkanowy.data.repositories.SessionRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.schedulers.SchedulersManager import org.threeten.bp.LocalDate +import org.threeten.bp.LocalDate.now +import org.threeten.bp.LocalDate.ofEpochDay +import java.util.concurrent.TimeUnit.MILLISECONDS import javax.inject.Inject class AttendancePresenter @Inject constructor( @@ -19,79 +20,79 @@ class AttendancePresenter @Inject constructor( private val sessionRepository: SessionRepository ) : BasePresenter(errorHandler) { - var currentDate: LocalDate = LocalDate.now().nearSchoolDayPrevOnWeekEnd + lateinit var currentDate: LocalDate private set - override fun attachView(view: AttendanceView) { - super.attachView(view) + fun onAttachView(view: AttendanceView, date: Long?) { + super.onAttachView(view) view.initView() + loadData(ofEpochDay(date ?: now().previousOrSameSchoolDay.toEpochDay())) + reloadView() } - fun loadAttendanceForPreviousDay() = loadData(currentDate.previousWorkDay.toEpochDay()) - - fun loadAttendanceForNextDay() = loadData(currentDate.nextWorkDay.toEpochDay()) - - fun loadData(date: Long?, forceRefresh: Boolean = false) { - this.currentDate = LocalDate.ofEpochDay(date - ?: currentDate.nearSchoolDayPrevOnWeekEnd.toEpochDay()) - if (currentDate.isHolidays) return - - disposable.clear() - disposable.add(sessionRepository.getSemesters() - .map { selectSemester(it, -1) } - .flatMap { attendanceRepository.getAttendance(it, currentDate, currentDate, forceRefresh) } - .map { createAttendanceItems(it) } - .subscribeOn(schedulers.backgroundThread()) - .observeOn(schedulers.mainThread()) - .doOnSubscribe { - view?.run { - showRefresh(forceRefresh) - showProgress(!forceRefresh) - if (!forceRefresh) { - showEmpty(false) - clearData() - } - showPreButton(!currentDate.minusDays(1).isHolidays) - showNextButton(!currentDate.plusDays(1).isHolidays) - updateNavigationDay(currentDate.toFormattedString("EEEE \n dd.MM.YYYY").capitalize()) - } - } - .doFinally { - view?.run { - showRefresh(false) - showProgress(false) - } - } - .subscribe({ - view?.run { - showEmpty(it.isEmpty()) - showContent(it.isNotEmpty()) - updateData(it) - } - }) { - view?.run { showEmpty(isViewEmpty()) } - errorHandler.proceed(it) - }) + fun onPreviousDay() { + loadData(currentDate.previousSchoolDay) + reloadView() } - private fun createAttendanceItems(items: List): List { - return items.map { - AttendanceItem().apply { attendance = it } - } + fun onNextDay() { + loadData(currentDate.nextSchoolDay) + reloadView() + } + + fun onSwipeRefresh() { + loadData(currentDate, true) + } + + fun onViewReselected() { + loadData(now().previousOrSameSchoolDay) + reloadView() } fun onAttendanceItemSelected(item: AbstractFlexibleItem<*>?) { if (item is AttendanceItem) view?.showAttendanceDialog(item.attendance) } - private fun selectSemester(semesters: List, index: Int): Semester { - return semesters.single { it.current }.let { currentSemester -> - if (index == -1) currentSemester - else semesters.single { semester -> - semester.run { - semesterName - 1 == index && diaryId == currentSemester.diaryId - } - } + private fun loadData(date: LocalDate, forceRefresh: Boolean = false) { + currentDate = date + disposable.apply { + clear() + add(sessionRepository.getSemesters() + .delay(200, MILLISECONDS) + .map { it.single { semester -> semester.current } } + .flatMap { attendanceRepository.getAttendance(it, date, date, forceRefresh) } + .map { items -> items.map { AttendanceItem(it) } } + .subscribeOn(schedulers.backgroundThread()) + .observeOn(schedulers.mainThread()) + .doFinally { + view?.run { + hideRefresh() + showProgress(false) + } + } + .subscribe({ + view?.apply { + updateData(it) + showEmpty(it.isEmpty()) + showContent(it.isNotEmpty()) + } + }) { + view?.run { showEmpty(isViewEmpty()) } + errorHandler.proceed(it) + } + ) + } + } + + private fun reloadView() { + view?.apply { + showProgress(true) + showContent(false) + showEmpty(false) + clearData() + showNextButton(!currentDate.plusDays(1).isHolidays) + showPreButton(!currentDate.minusDays(1).isHolidays) + updateNavigationDay(currentDate.toFormattedString("EEEE \n dd.MM.YYYY").capitalize()) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceView.kt b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceView.kt index 7a5b537b6..f4d0a433d 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/attendance/AttendanceView.kt @@ -9,20 +9,20 @@ interface AttendanceView : BaseView { fun updateData(data: List) - fun clearData() - fun updateNavigationDay(date: String) + fun clearData() + fun isViewEmpty(): Boolean + fun hideRefresh() + fun showEmpty(show: Boolean) fun showProgress(show: Boolean) fun showContent(show: Boolean) - fun showRefresh(show: Boolean) - fun showPreButton(show: Boolean) fun showNextButton(show: Boolean) diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamDialog.kt index 619b29e11..4a833cad1 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamDialog.kt @@ -26,14 +26,13 @@ class ExamDialog : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(DialogFragment.STYLE_NORMAL, R.style.DialogFragmentTheme) + setStyle(STYLE_NO_TITLE, 0) arguments?.run { exam = getSerializable(ARGUMENT_KEY) as Exam } } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - dialog.setTitle(getString(R.string.all_details)) return inflater.inflate(R.layout.dialog_exam, container, false) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamFragment.kt index 94c781337..444881f16 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamFragment.kt @@ -11,11 +11,12 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.main.MainView import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.fragment_exam.* import javax.inject.Inject -class ExamFragment : BaseFragment(), ExamView { +class ExamFragment : BaseFragment(), ExamView, MainView.MenuFragmentView { @Inject lateinit var presenter: ExamPresenter @@ -34,10 +35,8 @@ class ExamFragment : BaseFragment(), ExamView { override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - presenter.run { - attachView(this@ExamFragment) - loadData(date = savedInstanceState?.getLong(SAVED_DATE_KEY)) - } + messageContainer = examRecycler + presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY)) } override fun initView() { @@ -48,9 +47,13 @@ class ExamFragment : BaseFragment(), ExamView { layoutManager = SmoothScrollLinearLayoutManager(context) adapter = examAdapter } - examSwipe.setOnRefreshListener { presenter.loadData(date = null, forceRefresh = true) } - examPreviousButton.setOnClickListener { presenter.loadExamsForPreviousWeek() } - examNextButton.setOnClickListener { presenter.loadExamsForNextWeek()} + examSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } + examPreviousButton.setOnClickListener { presenter.onPreviousWeek() } + examNextButton.setOnClickListener { presenter.onNextWeek() } + } + + override fun hideRefresh() { + examSwipe.isRefreshing = false } override fun updateData(data: List) { @@ -61,6 +64,16 @@ class ExamFragment : BaseFragment(), ExamView { examNavDate.text = date } + override fun clearData() { + examAdapter.clear() + } + + override fun isViewEmpty() = examAdapter.isEmpty + + override fun onFragmentReselected() { + presenter.onViewReselected() + } + override fun showEmpty(show: Boolean) { examEmpty.visibility = if (show) VISIBLE else GONE } @@ -73,10 +86,6 @@ class ExamFragment : BaseFragment(), ExamView { examRecycler.visibility = if (show) VISIBLE else GONE } - override fun showRefresh(show: Boolean) { - examSwipe.isRefreshing = show - } - override fun showPreButton(show: Boolean) { examPreviousButton.visibility = if (show) VISIBLE else INVISIBLE } @@ -95,7 +104,7 @@ class ExamFragment : BaseFragment(), ExamView { } override fun onDestroyView() { + presenter.onDetachView() super.onDestroyView() - presenter.detachView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamHeader.kt b/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamHeader.kt index 57a8e778b..45be5f1da 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamHeader.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamHeader.kt @@ -12,15 +12,21 @@ import kotlinx.android.extensions.LayoutContainer import kotlinx.android.synthetic.main.header_exam.* import org.threeten.bp.LocalDate -class ExamHeader : AbstractHeaderItem() { +class ExamHeader(private val date: LocalDate) : AbstractHeaderItem() { - lateinit var date: LocalDate + override fun getLayoutRes() = R.layout.header_exam override fun createViewHolder(view: View?, adapter: FlexibleAdapter>?): ViewHolder { return ViewHolder(view, adapter) } - override fun getLayoutRes() = R.layout.header_exam + override fun bindViewHolder(adapter: FlexibleAdapter>?, holder: ViewHolder, + position: Int, payloads: MutableList?) { + holder.run { + examHeaderDay.text = date.weekDayName.capitalize() + examHeaderDate.text = date.toFormattedString() + } + } override fun equals(other: Any?): Boolean { if (this === other) return true @@ -37,21 +43,9 @@ class ExamHeader : AbstractHeaderItem() { return date.hashCode() } - override fun bindViewHolder(adapter: FlexibleAdapter>?, holder: ViewHolder, - position: Int, payloads: MutableList?) { - holder.run { - examHeaderDay.text = date.weekDayName.capitalize() - examHeaderDate.text = date.toFormattedString() - } - } - class ViewHolder(view: View?, adapter: FlexibleAdapter>?) : ExpandableViewHolder(view, adapter), LayoutContainer { - init { - contentView.setOnClickListener(this) - } - override val containerView: View get() = contentView } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamItem.kt b/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamItem.kt index bb7a503de..f9d05d47e 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamItem.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamItem.kt @@ -13,21 +13,6 @@ import kotlinx.android.synthetic.main.item_exam.* class ExamItem(header: ExamHeader, val exam: Exam) : AbstractSectionableItem(header) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as ExamItem - - if (exam != other.exam) return false - - return true - } - - override fun hashCode(): Int { - return exam.hashCode() - } - override fun getLayoutRes() = R.layout.item_exam override fun createViewHolder(view: View, adapter: FlexibleAdapter>): ViewHolder { @@ -43,6 +28,21 @@ class ExamItem(header: ExamHeader, val exam: Exam) : AbstractSectionableItem) : FlexibleViewHolder(view, adapter), LayoutContainer { diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamPresenter.kt index a1b96b13f..9df5ba750 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamPresenter.kt @@ -3,15 +3,15 @@ package io.github.wulkanowy.ui.main.exam import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.data.ErrorHandler import io.github.wulkanowy.data.db.entities.Exam -import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.repositories.ExamRepository import io.github.wulkanowy.data.repositories.SessionRepository import io.github.wulkanowy.ui.base.BasePresenter -import io.github.wulkanowy.utils.isHolidays +import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.schedulers.SchedulersManager -import io.github.wulkanowy.utils.toFormattedString -import io.github.wulkanowy.utils.weekFirstDayNextOnWeekEnd import org.threeten.bp.LocalDate +import org.threeten.bp.LocalDate.now +import org.threeten.bp.LocalDate.ofEpochDay +import java.util.concurrent.TimeUnit.MILLISECONDS import javax.inject.Inject class ExamPresenter @Inject constructor( @@ -21,79 +21,89 @@ class ExamPresenter @Inject constructor( private val sessionRepository: SessionRepository ) : BasePresenter(errorHandler) { - var currentDate: LocalDate = LocalDate.now().weekFirstDayNextOnWeekEnd + lateinit var currentDate: LocalDate private set - override fun attachView(view: ExamView) { - super.attachView(view) + fun onAttachView(view: ExamView, date: Long?) { + super.onAttachView(view) view.initView() + loadData(ofEpochDay(date ?: now().nextOrSameSchoolDay.toEpochDay())) + reloadView() } - fun loadExamsForPreviousWeek() = loadData(currentDate.minusDays(7).toEpochDay()) - - fun loadExamsForNextWeek() = loadData(currentDate.plusDays(7).toEpochDay()) - - fun loadData(date: Long?, forceRefresh: Boolean = false) { - this.currentDate = LocalDate.ofEpochDay(date - ?: currentDate.weekFirstDayNextOnWeekEnd.toEpochDay()) - if (currentDate.isHolidays) return - - disposable.clear() - disposable.add(sessionRepository.getSemesters() - .map { selectSemester(it, -1) } - .flatMap { examRepository.getExams(it, currentDate, currentDate.plusDays(4), forceRefresh) } - .map { it.groupBy { exam -> exam.date }.toSortedMap() } - .map { createExamItems(it) } - .subscribeOn(schedulers.backgroundThread()) - .observeOn(schedulers.mainThread()) - .doOnSubscribe { - view?.run { - showRefresh(forceRefresh) - showProgress(!forceRefresh) - if (!forceRefresh) showEmpty(false) - showContent(null == date && forceRefresh) - showPreButton(!currentDate.minusDays(7).isHolidays) - showNextButton(!currentDate.plusDays(7).isHolidays) - updateNavigationWeek(currentDate.toFormattedString("dd.MM") + - "-${currentDate.plusDays(4).toFormattedString("dd.MM")}") - } - } - .doAfterSuccess { - view?.run { - showEmpty(it.isEmpty()) - showContent(it.isNotEmpty()) - } - } - .doFinally { - view?.run { - showRefresh(false) - showProgress(false) - } - } - .subscribe({ view?.updateData(it) }) { errorHandler.proceed(it) }) + fun onPreviousWeek() { + loadData(currentDate.minusDays(7)) + reloadView() } - private fun createExamItems(items: Map>): List { - return items.flatMap { - val header = ExamHeader().apply { date = it.key } - it.value.reversed().map { item -> - ExamItem(header, item) - } - } + fun onNextWeek() { + loadData(currentDate.plusDays(7)) + reloadView() + } + + fun onSwipeRefresh() { + loadData(currentDate, true) } fun onExamItemSelected(item: AbstractFlexibleItem<*>?) { if (item is ExamItem) view?.showExamDialog(item.exam) } - private fun selectSemester(semesters: List, index: Int): Semester { - return semesters.single { it.current }.let { currentSemester -> - if (index == -1) currentSemester - else semesters.single { semester -> - semester.run { - semesterName - 1 == index && diaryId == currentSemester.diaryId - } + fun onViewReselected() { + loadData(now().nextOrSameSchoolDay) + reloadView() + } + + private fun loadData(date: LocalDate, forceRefresh: Boolean = false) { + currentDate = date + disposable.apply { + clear() + add(sessionRepository.getSemesters() + .delay(200, MILLISECONDS) + .map { it.single { semester -> semester.current } } + .flatMap { + examRepository.getExams(it, currentDate.monday, currentDate.friday, forceRefresh) + }.map { it.groupBy { exam -> exam.date }.toSortedMap() } + .map { createExamItems(it) } + .subscribeOn(schedulers.backgroundThread()) + .observeOn(schedulers.mainThread()) + .doFinally { + view?.run { + hideRefresh() + showProgress(false) + } + } + .subscribe({ + view?.apply { + updateData(it) + showEmpty(it.isEmpty()) + showContent(it.isNotEmpty()) + } + }) { + view?.run { showEmpty(isViewEmpty()) } + errorHandler.proceed(it) + }) + } + } + + private fun createExamItems(items: Map>): List { + return items.flatMap { + ExamHeader(it.key).let { header -> + it.value.reversed().map { item -> ExamItem(header, item) } } } } + + private fun reloadView() { + view?.apply { + showProgress(true) + showContent(false) + showEmpty(false) + clearData() + showPreButton(!currentDate.minusDays(7).isHolidays) + showNextButton(!currentDate.plusDays(7).isHolidays) + updateNavigationWeek("${currentDate.toFormattedString("dd.MM")} - " + + currentDate.plusDays(4).toFormattedString("dd.MM")) + } + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamView.kt b/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamView.kt index e97d40771..e00ab9bb6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/exam/ExamView.kt @@ -9,19 +9,23 @@ interface ExamView : BaseView { fun updateData(data: List) + fun updateNavigationWeek(date: String) + + fun clearData() + + fun isViewEmpty(): Boolean + + fun hideRefresh() + fun showEmpty(show: Boolean) fun showProgress(show: Boolean) fun showContent(show: Boolean) - fun showRefresh(show: Boolean) - fun showNextButton(show: Boolean) fun showPreButton(show: Boolean) fun showExamDialog(exam: Exam) - - fun updateNavigationWeek(date: String) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grade/GradeFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/main/grade/GradeFragment.kt index 97144f5ce..4b6675dda 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/grade/GradeFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grade/GradeFragment.kt @@ -24,6 +24,8 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MenuFragmentView { lateinit var pagerAdapter: BasePagerAdapter companion object { + private const val SAVED_SEMESTER_KEY = "CURRENT_SEMESTER" + fun newInstance() = GradeFragment() } @@ -38,7 +40,7 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MenuFragmentView { override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - presenter.attachView(this) + presenter.onAttachView(this, savedInstanceState?.getInt(SAVED_SEMESTER_KEY)) } override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) { @@ -113,8 +115,13 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MenuFragmentView { (pagerAdapter.registeredFragments[index] as? GradeView.GradeChildView)?.onParentChangeSemester() } + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putInt(SAVED_SEMESTER_KEY, presenter.selectedIndex) + } + override fun onDestroyView() { super.onDestroyView() - presenter.detachView() + presenter.onDetachView() } } \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grade/GradePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/main/grade/GradePresenter.kt index 3d79156a3..5916ad473 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/grade/GradePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grade/GradePresenter.kt @@ -6,7 +6,7 @@ import io.github.wulkanowy.data.repositories.SessionRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.utils.schedulers.SchedulersManager import io.reactivex.Completable -import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeUnit.MILLISECONDS import javax.inject.Inject class GradePresenter @Inject constructor( @@ -14,16 +14,18 @@ class GradePresenter @Inject constructor( private val schedulers: SchedulersManager, private val sessionRepository: SessionRepository) : BasePresenter(errorHandler) { - private var semesters = emptyList() + var selectedIndex = 0 + private set - private var selectedIndex = 0 + private var semesters = emptyList() private val loadedSemesterId = mutableMapOf() - override fun attachView(view: GradeView) { - super.attachView(view) - disposable.add(Completable.timer(150, TimeUnit.MILLISECONDS, schedulers.mainThread()) + fun onAttachView(view: GradeView, savedIndex: Int?) { + super.onAttachView(view) + disposable.add(Completable.timer(150, MILLISECONDS, schedulers.mainThread()) .subscribe { + selectedIndex = savedIndex ?: 0 view.initView() loadData() }) @@ -34,13 +36,13 @@ class GradePresenter @Inject constructor( } fun onSemesterSwitch(): Boolean { - if (semesters.isNotEmpty()) view?.showSemesterDialog(selectedIndex) + if (semesters.isNotEmpty()) view?.showSemesterDialog(selectedIndex - 1) return true } fun onSemesterSelected(index: Int) { - if (selectedIndex != index) { - selectedIndex = index + if (selectedIndex != index - 1) { + selectedIndex = index + 1 loadedSemesterId.clear() view?.let { notifyChildrenSemesterChange() @@ -67,9 +69,9 @@ class GradePresenter @Inject constructor( private fun loadData() { disposable.add(sessionRepository.getSemesters() - .map { + .doOnSuccess { it.first { item -> item.current }.also { current -> - selectedIndex = current.semesterName - 1 + selectedIndex = if (selectedIndex == 0) current.semesterName else selectedIndex semesters = it.filter { semester -> semester.diaryId == current.diaryId } } } @@ -81,7 +83,7 @@ class GradePresenter @Inject constructor( } private fun loadChild(index: Int, forceRefresh: Boolean = false) { - semesters.first { it.semesterName == selectedIndex + 1 }.semesterId.also { + semesters.first { it.semesterName == selectedIndex }.semesterId.also { if (forceRefresh || loadedSemesterId[index] != it) { view?.notifyChildLoadData(index, it, forceRefresh) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsDialog.kt index 3fda90d93..f08612263 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsDialog.kt @@ -30,6 +30,7 @@ class GradeDetailsDialog : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + setStyle(STYLE_NO_TITLE, 0) arguments?.run { grade = getSerializable(ARGUMENT_KEY) as Grade } @@ -48,7 +49,7 @@ class GradeDetailsDialog : DialogFragment() { gradeDialogColorValue.text = getString(grade.colorStringId) gradeDialogCommentValue.apply { - if (grade.comment.isEmpty()) { + if (grade.comment.isBlank()) { visibility = GONE gradeDialogComment.visibility = GONE } else text = grade.comment @@ -59,15 +60,15 @@ class GradeDetailsDialog : DialogFragment() { setBackgroundResource(grade.valueColor) } - gradeDialogTeacherValue.text = if (grade.teacher.isEmpty()) { + gradeDialogTeacherValue.text = if (grade.teacher.isBlank()) { getString(R.string.all_no_data) } else grade.teacher gradeDialogDescriptionValue.text = grade.run { when { - description.isEmpty() && gradeSymbol.isNotEmpty() -> gradeSymbol - description.isEmpty() && gradeSymbol.isEmpty() -> getString(R.string.all_no_description) - gradeSymbol.isNotEmpty() && description.isNotEmpty() -> "$gradeSymbol - $description" + description.isBlank() && gradeSymbol.isNotBlank() -> gradeSymbol + description.isBlank() && gradeSymbol.isBlank() -> getString(R.string.all_no_description) + gradeSymbol.isNotBlank() && description.isNotBlank() -> "$gradeSymbol - $description" else -> description } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsFragment.kt index a5f3409c1..79d31b7b4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsFragment.kt @@ -37,7 +37,8 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - presenter.attachView(this) + messageContainer = gradeDetailsRecycler + presenter.onAttachView(this) } override fun initView() { @@ -47,6 +48,8 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh setOnItemClickListener { presenter.onGradeItemSelected(getItem(it)) } } + gradeDetailsAdapter.getItemCountOfTypes() + gradeDetailsRecycler.run { layoutManager = SmoothScrollLinearLayoutManager(context) adapter = gradeDetailsAdapter @@ -100,7 +103,7 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh } override fun onParentLoadData(semesterId: String, forceRefresh: Boolean) { - presenter.loadData(semesterId, forceRefresh) + presenter.onParentViewLoadData(semesterId, forceRefresh) } override fun onParentReselected() { @@ -108,7 +111,7 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh } override fun onParentChangeSemester() { - presenter.onParentChangeSemester() + presenter.onParentViewChangeSemester() } override fun notifyParentDataLoaded(semesterId: String) { @@ -129,6 +132,6 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh override fun onDestroyView() { super.onDestroyView() - presenter.detachView() + presenter.onDetachView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsItem.kt b/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsItem.kt index 1a553b1e7..4154a6283 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsItem.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsItem.kt @@ -31,7 +31,7 @@ class GradeDetailsItem(val grade: Grade, private val weightString: String, priva text = grade.entry setBackgroundResource(valueColor) } - gradeItemDescription.text = if (grade.description.isNotEmpty()) grade.description else grade.gradeSymbol + gradeItemDescription.text = if (grade.description.isNotBlank()) grade.description else grade.gradeSymbol gradeItemDate.text = grade.date.toFormattedString() gradeItemWeight.text = "$weightString: ${grade.weight}" gradeItemNote.visibility = if (grade.isNew) VISIBLE else GONE diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsPresenter.kt index 8add69a9e..ec0280744 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grade/details/GradeDetailsPresenter.kt @@ -17,12 +17,12 @@ class GradeDetailsPresenter @Inject constructor( private val gradeRepository: GradeRepository, private val sessionRepository: SessionRepository) : BasePresenter(errorHandler) { - override fun attachView(view: GradeDetailsView) { - super.attachView(view) + override fun onAttachView(view: GradeDetailsView) { + super.onAttachView(view) view.initView() } - fun loadData(semesterId: String, forceRefresh: Boolean) { + fun onParentViewLoadData(semesterId: String, forceRefresh: Boolean) { disposable.add(sessionRepository.getSemesters() .flatMap { gradeRepository.getGrades(it.first { item -> item.semesterId == semesterId }, forceRefresh) } .map { createGradeItems(it.groupBy { grade -> grade.subject }.toSortedMap()) } @@ -76,7 +76,7 @@ class GradeDetailsPresenter @Inject constructor( } } - fun onParentChangeSemester() { + fun onParentViewChangeSemester() { view?.run { showProgress(true) showRefresh(false) diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryFragment.kt index f64c1f363..e91c97f51 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryFragment.kt @@ -33,7 +33,8 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - presenter.attachView(this) + messageContainer = gradeSummaryRecycler + presenter.onAttachView(this) } override fun initView() { @@ -81,7 +82,7 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh } override fun onParentLoadData(semesterId: String, forceRefresh: Boolean) { - presenter.loadData(semesterId, forceRefresh) + presenter.onParentViewLoadData(semesterId, forceRefresh) } override fun onParentReselected() { @@ -89,7 +90,7 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh } override fun onParentChangeSemester() { - presenter.onParentChangeSemester() + presenter.onParentViewChangeSemester() } override fun notifyParentDataLoaded(semesterId: String) { @@ -106,6 +107,6 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh override fun onDestroyView() { super.onDestroyView() - presenter.detachView() + presenter.onDetachView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryPresenter.kt index 0c3f135df..076868658 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/grade/summary/GradeSummaryPresenter.kt @@ -20,12 +20,12 @@ class GradeSummaryPresenter @Inject constructor( private val schedulers: SchedulersManager) : BasePresenter(errorHandler) { - override fun attachView(view: GradeSummaryView) { - super.attachView(view) + override fun onAttachView(view: GradeSummaryView) { + super.onAttachView(view) view.initView() } - fun loadData(semesterId: String, forceRefresh: Boolean) { + fun onParentViewLoadData(semesterId: String, forceRefresh: Boolean) { disposable.add(sessionRepository.getSemesters() .map { semester -> semester.first { it.semesterId == semesterId } } .flatMap { @@ -76,7 +76,7 @@ class GradeSummaryPresenter @Inject constructor( } } - fun onParentChangeSemester() { + fun onParentViewChangeSemester() { view?.run { showProgress(true) showRefresh(false) @@ -110,12 +110,12 @@ class GradeSummaryPresenter @Inject constructor( private fun checkEmpty(gradeSummary: GradeSummary, averages: Map): Boolean { return gradeSummary.run { - finalGrade.isEmpty() && predictedGrade.isEmpty() && averages[subject] == null + finalGrade.isBlank() && predictedGrade.isBlank() && averages[subject] == null } } private fun formatAverage(average: Double, defaultValue: String = "-- --"): String { - return if (average == 0.0 || average.isNaN()) defaultValue + return if (average == 0.0) defaultValue else format(FRANCE, "%.2f", average) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableDialog.kt b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableDialog.kt index 1bd82f59e..c2e2ff15a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableDialog.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableDialog.kt @@ -28,14 +28,13 @@ class TimetableDialog : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setStyle(DialogFragment.STYLE_NORMAL, R.style.DialogFragmentTheme) + setStyle(STYLE_NO_TITLE, 0) arguments?.run { lesson = getSerializable(ARGUMENT_KEY) as Timetable } } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - dialog.setTitle(getString(R.string.all_details)) return inflater.inflate(R.layout.dialog_timetable, container, false) } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragment.kt index 157b7b487..058ee1a8c 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableFragment.kt @@ -10,11 +10,12 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.R import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.main.MainView import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.fragment_timetable.* import javax.inject.Inject -class TimetableFragment : BaseFragment(), TimetableView { +class TimetableFragment : BaseFragment(), TimetableView, MainView.MenuFragmentView { @Inject lateinit var presenter: TimetablePresenter @@ -24,6 +25,7 @@ class TimetableFragment : BaseFragment(), TimetableView { companion object { private const val SAVED_DATE_KEY = "CURRENT_DATE" + fun newInstance() = TimetableFragment() } @@ -33,25 +35,22 @@ class TimetableFragment : BaseFragment(), TimetableView { override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - presenter.run { - attachView(this@TimetableFragment) - loadData(date = savedInstanceState?.getLong(SAVED_DATE_KEY)) - } + messageContainer = timetableRecycler + presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY)) } override fun initView() { timetableAdapter.run { - isAutoCollapseOnExpand = true - isAutoScrollOnExpand = true - setOnItemClickListener { presenter.onTimetableItemSelected(getItem(it))} + setOnItemClickListener { presenter.onTimetableItemSelected(getItem(it)) } } + timetableRecycler.run { layoutManager = SmoothScrollLinearLayoutManager(context) adapter = timetableAdapter } - timetableSwipe.setOnRefreshListener { presenter.loadData(date = null, forceRefresh = true) } - timetablePreviousButton.setOnClickListener { presenter.loadTimetableForPreviousDay() } - timetableNextButton.setOnClickListener { presenter.loadTimetableForNextDay() } + timetableSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } + timetablePreviousButton.setOnClickListener { presenter.onPreviousDay() } + timetableNextButton.setOnClickListener { presenter.onNextDay() } } override fun updateData(data: List) { @@ -68,6 +67,14 @@ class TimetableFragment : BaseFragment(), TimetableView { override fun isViewEmpty() = timetableAdapter.isEmpty + override fun hideRefresh() { + timetableSwipe.isRefreshing = false + } + + override fun onFragmentReselected() { + presenter.onViewReselected() + } + override fun showEmpty(show: Boolean) { timetableEmpty.visibility = if (show) View.VISIBLE else View.GONE } @@ -80,10 +87,6 @@ class TimetableFragment : BaseFragment(), TimetableView { timetableRecycler.visibility = if (show) View.VISIBLE else View.GONE } - override fun showRefresh(show: Boolean) { - timetableSwipe.isRefreshing = show - } - override fun showPreButton(show: Boolean) { timetablePreviousButton.visibility = if (show) View.VISIBLE else View.INVISIBLE } @@ -96,13 +99,15 @@ class TimetableFragment : BaseFragment(), TimetableView { TimetableDialog.newInstance(lesson).show(fragmentManager, lesson.toString()) } + override fun roomString() = getString(R.string.timetable_room) + override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay()) } override fun onDestroyView() { + presenter.onDetachView() super.onDestroyView() - presenter.detachView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableItem.kt b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableItem.kt index cfd75cb88..0d61f6985 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableItem.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableItem.kt @@ -15,15 +15,29 @@ import io.github.wulkanowy.utils.toFormattedString import kotlinx.android.extensions.LayoutContainer import kotlinx.android.synthetic.main.item_timetable.* -class TimetableItem : AbstractFlexibleItem() { +class TimetableItem(val lesson: Timetable, private val roomText: String) + : AbstractFlexibleItem() { - lateinit var lesson: Timetable + override fun getLayoutRes(): Int = R.layout.item_timetable override fun createViewHolder(view: View, adapter: FlexibleAdapter>): ViewHolder { return ViewHolder(view, adapter) } - override fun getLayoutRes(): Int = R.layout.item_timetable + @SuppressLint("SetTextI18n") + override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, + position: Int, payloads: MutableList?) { + holder.apply { + timetableItemNumber.text = lesson.number.toString() + timetableItemSubject.text = lesson.subject + timetableItemRoom.text = if (lesson.room.isNotBlank()) "$roomText ${lesson.room}" else "" + timetableItemTime.text = "${lesson.start.toFormattedString("HH:mm")} - ${lesson.end.toFormattedString("HH:mm")}" + timetableItemAlert.visibility = if (lesson.changes || lesson.canceled) VISIBLE else GONE + timetableItemSubject.paintFlags = + if (lesson.canceled) timetableItemSubject.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG + else timetableItemSubject.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv() + } + } override fun equals(other: Any?): Boolean { if (this === other) return true @@ -39,27 +53,10 @@ class TimetableItem : AbstractFlexibleItem() { return lesson.hashCode() } - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: ViewHolder, - position: Int, payloads: MutableList?) { - holder.bind(lesson) - } - class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer { override val containerView: View get() = contentView - - @SuppressLint("SetTextI18n") - fun bind(lesson: Timetable) { - timetableItemNumber.text = lesson.number.toString() - timetableItemSubject.text = lesson.subject - timetableItemRoom.text = if (lesson.room.isNotBlank()) "${view.context.getString(R.string.timetable_room)} ${lesson.room}" else "" - timetableItemTime.text = "${lesson.start.toFormattedString("HH:mm")} - ${lesson.end.toFormattedString("HH:mm")}" - timetableItemAlert.visibility = if (lesson.changes || lesson.canceled) VISIBLE else GONE - timetableItemSubject.paintFlags = - if (lesson.canceled) timetableItemSubject.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG - else timetableItemSubject.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv() - } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePresenter.kt index 98aa0c71b..d91376bb3 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetablePresenter.kt @@ -2,14 +2,15 @@ package io.github.wulkanowy.ui.main.timetable import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.data.ErrorHandler -import io.github.wulkanowy.data.db.entities.Semester -import io.github.wulkanowy.data.db.entities.Timetable import io.github.wulkanowy.data.repositories.SessionRepository import io.github.wulkanowy.data.repositories.TimetableRepository import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.utils.* import io.github.wulkanowy.utils.schedulers.SchedulersManager import org.threeten.bp.LocalDate +import org.threeten.bp.LocalDate.now +import org.threeten.bp.LocalDate.ofEpochDay +import java.util.concurrent.TimeUnit.MILLISECONDS import javax.inject.Inject class TimetablePresenter @Inject constructor( @@ -19,75 +20,78 @@ class TimetablePresenter @Inject constructor( private val sessionRepository: SessionRepository ) : BasePresenter(errorHandler) { - var currentDate: LocalDate = LocalDate.now().nearSchoolDayNextOnWeekEnd + lateinit var currentDate: LocalDate private set - override fun attachView(view: TimetableView) { - super.attachView(view) + fun onAttachView(view: TimetableView, date: Long?) { + super.onAttachView(view) view.initView() + loadData(ofEpochDay(date ?: now().nextOrSameSchoolDay.toEpochDay())) + reloadView() } - fun loadTimetableForPreviousDay() = loadData(currentDate.previousWorkDay.toEpochDay()) - - fun loadTimetableForNextDay() = loadData(currentDate.nextWorkDay.toEpochDay()) - - fun loadData(date: Long?, forceRefresh: Boolean = false) { - this.currentDate = LocalDate.ofEpochDay(date ?: currentDate.nearSchoolDayNextOnWeekEnd.toEpochDay()) - if (currentDate.isHolidays) return - - disposable.clear() - disposable.add(sessionRepository.getSemesters() - .map { selectSemester(it, -1) } - .flatMap { timetableRepository.getTimetable(it, currentDate, currentDate, forceRefresh) } - .map { createTimetableItems(it) } - .subscribeOn(schedulers.backgroundThread()) - .observeOn(schedulers.mainThread()) - .doOnSubscribe { - view?.run { - showRefresh(forceRefresh) - showProgress(!forceRefresh) - if (!forceRefresh) clearData() - showPreButton(!currentDate.minusDays(1).isHolidays) - showNextButton(!currentDate.plusDays(1).isHolidays) - updateNavigationDay(currentDate.toFormattedString("EEEE \n dd.MM.YYYY").capitalize()) - } - } - .doFinally { - view?.run { - showRefresh(false) - showProgress(false) - } - } - .subscribe({ - view?.run { - showEmpty(it.isEmpty()) - showContent(it.isNotEmpty()) - updateData(it) - } - }) { - view?.run { showEmpty(isViewEmpty()) } - errorHandler.proceed(it) - }) + fun onPreviousDay() { + loadData(currentDate.previousSchoolDay) + reloadView() } - private fun createTimetableItems(items: List): List { - return items.map { - TimetableItem().apply { lesson = it } - } + fun onNextDay() { + loadData(currentDate.nextSchoolDay) + reloadView() + } + + fun onSwipeRefresh() { + loadData(currentDate, true) + } + + fun onViewReselected() { + loadData(now().nextOrSameSchoolDay) + reloadView() } fun onTimetableItemSelected(item: AbstractFlexibleItem<*>?) { if (item is TimetableItem) view?.showTimetableDialog(item.lesson) } - private fun selectSemester(semesters: List, index: Int): Semester { - return semesters.single { it.current }.let { currentSemester -> - if (index == -1) currentSemester - else semesters.single { semester -> - semester.run { - semesterName - 1 == index && diaryId == currentSemester.diaryId - } - } + private fun loadData(date: LocalDate, forceRefresh: Boolean = false) { + currentDate = date + disposable.apply { + clear() + add(sessionRepository.getSemesters() + .delay(200, MILLISECONDS) + .map { it.single { semester -> semester.current } } + .flatMap { timetableRepository.getTimetable(it, currentDate, currentDate, forceRefresh) } + .map { items -> items.map { TimetableItem(it, view?.roomString().orEmpty()) } } + .subscribeOn(schedulers.backgroundThread()) + .observeOn(schedulers.mainThread()) + .doFinally { + view?.run { + hideRefresh() + showProgress(false) + } + } + .subscribe({ + view?.apply { + updateData(it) + showEmpty(it.isEmpty()) + showContent(it.isNotEmpty()) + } + }) { + view?.run { showEmpty(isViewEmpty()) } + errorHandler.proceed(it) + }) + } + } + + private fun reloadView() { + view?.apply { + showProgress(true) + showContent(false) + showEmpty(false) + clearData() + showNextButton(!currentDate.plusDays(1).isHolidays) + showPreButton(!currentDate.minusDays(1).isHolidays) + updateNavigationDay(currentDate.toFormattedString("EEEE \n dd.MM.YYYY").capitalize()) } } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableView.kt b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableView.kt index a4e1c3603..c04a290bd 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/main/timetable/TimetableView.kt @@ -11,20 +11,23 @@ interface TimetableView : BaseView { fun updateNavigationDay(date: String) + fun isViewEmpty(): Boolean + + fun clearData() + + fun hideRefresh() + fun showEmpty(show: Boolean) fun showProgress(show: Boolean) fun showContent(show: Boolean) - fun showRefresh(show: Boolean) - fun showPreButton(show: Boolean) fun showNextButton(show: Boolean) fun showTimetableDialog(lesson: Timetable) - fun isViewEmpty(): Boolean - fun clearData() + fun roomString(): String } diff --git a/app/src/main/java/io/github/wulkanowy/ui/splash/SplashActivity.kt b/app/src/main/java/io/github/wulkanowy/ui/splash/SplashActivity.kt index f3cf93d9e..42fe39581 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/splash/SplashActivity.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/splash/SplashActivity.kt @@ -13,12 +13,7 @@ class SplashActivity : BaseActivity(), SplashView { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - presenter.attachView(this) - } - - override fun onDestroy() { - super.onDestroy() - presenter.detachView() + presenter.onAttachView(this) } override fun openLoginView() { @@ -30,4 +25,9 @@ class SplashActivity : BaseActivity(), SplashView { startActivity(MainActivity.getStartIntent(this)) finish() } + + override fun onDestroy() { + presenter.onDetachView() + super.onDestroy() + } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/splash/SplashPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/splash/SplashPresenter.kt index 0a22284c7..a04666ba7 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/splash/SplashPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/splash/SplashPresenter.kt @@ -9,8 +9,8 @@ class SplashPresenter @Inject constructor(private val sessionRepository: Session errorHandler: ErrorHandler) : BasePresenter(errorHandler) { - override fun attachView(view: SplashView) { - super.attachView(view) + override fun onAttachView(view: SplashView) { + super.onAttachView(view) view.run { if (sessionRepository.isSessionSaved) openMainView() else openLoginView() } } } diff --git a/app/src/main/java/io/github/wulkanowy/utils/GradeExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/GradeExtension.kt index ee89de03e..c9119cc3d 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/GradeExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/GradeExtension.kt @@ -19,7 +19,7 @@ fun List.calcAverage(): Double { fun List.calcAverage(): Double { return asSequence().mapNotNull { if (it.finalGrade.matches("[0-6]".toRegex())) it.finalGrade.toDouble() else null - }.average() + }.average().let { if (it.isNaN()) 0.0 else it } } inline val Grade.valueColor: Int @@ -43,6 +43,7 @@ inline val Grade.colorStringId: Int "F04C4C" -> R.string.all_red "20A4F7" -> R.string.all_blue "6ECD07" -> R.string.all_green + "B16CF1" -> R.string.all_purple else -> R.string.all_empty_color } } \ No newline at end of file diff --git a/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt index 2501e013f..0e832f96c 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt @@ -1,23 +1,24 @@ package io.github.wulkanowy.utils import org.threeten.bp.DayOfWeek.* +import org.threeten.bp.Instant import org.threeten.bp.LocalDate import org.threeten.bp.LocalDateTime +import org.threeten.bp.ZoneId import org.threeten.bp.format.DateTimeFormatter import org.threeten.bp.format.DateTimeFormatter.ofPattern -import org.threeten.bp.temporal.TemporalAdjusters import org.threeten.bp.temporal.TemporalAdjusters.* -import java.text.SimpleDateFormat import java.util.* -private const val DATE_PATTERN = "yyyy-MM-dd" +private const val DATE_PATTERN = "dd.MM.yyyy" fun Date.toLocalDate(): LocalDate { - return LocalDate.parse(SimpleDateFormat(DATE_PATTERN, Locale.getDefault()).format(this)) + return Instant.ofEpochMilli(this.time).atZone(ZoneId.systemDefault()).toLocalDate() } -fun Date.toLocalDateTime(): LocalDateTime = LocalDateTime.parse(SimpleDateFormat("yyyy-MM-dd HH:mm:ss", - Locale.getDefault()).format(this), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) +fun Date.toLocalDateTime(): LocalDateTime { + return Instant.ofEpochMilli(this.time).atZone(ZoneId.systemDefault()).toLocalDateTime() +} fun String.toLocalDate(format: String = DATE_PATTERN): LocalDate { return LocalDate.parse(this, DateTimeFormatter.ofPattern(format)) @@ -25,9 +26,9 @@ fun String.toLocalDate(format: String = DATE_PATTERN): LocalDate { fun LocalDate.toFormattedString(format: String = DATE_PATTERN): String = this.format(ofPattern(format)) -fun LocalDateTime.toFormattedString(format: String = DATE_PATTERN): String = this.format(DateTimeFormatter.ofPattern(format)) +fun LocalDateTime.toFormattedString(format: String = DATE_PATTERN): String = this.format(ofPattern(format)) -inline val LocalDate.nextWorkDay: LocalDate +inline val LocalDate.nextSchoolDay: LocalDate get() { return when (this.dayOfWeek) { FRIDAY, SATURDAY, SUNDAY -> this.with(next(MONDAY)) @@ -35,7 +36,7 @@ inline val LocalDate.nextWorkDay: LocalDate } } -inline val LocalDate.previousWorkDay: LocalDate +inline val LocalDate.previousSchoolDay: LocalDate get() { return when (this.dayOfWeek) { SATURDAY, SUNDAY, MONDAY -> this.with(previous(FRIDAY)) @@ -43,7 +44,15 @@ inline val LocalDate.previousWorkDay: LocalDate } } -inline val LocalDate.nearSchoolDayPrevOnWeekEnd: LocalDate +inline val LocalDate.nextOrSameSchoolDay: LocalDate + get() { + return when (this.dayOfWeek) { + SATURDAY, SUNDAY -> this.with(next(MONDAY)) + else -> this + } + } + +inline val LocalDate.previousOrSameSchoolDay: LocalDate get() { return when (this.dayOfWeek) { SATURDAY, SUNDAY -> this.with(previous(FRIDAY)) @@ -51,27 +60,14 @@ inline val LocalDate.nearSchoolDayPrevOnWeekEnd: LocalDate } } -inline val LocalDate.nearSchoolDayNextOnWeekEnd: LocalDate - get() { - return when (this.dayOfWeek) { - SATURDAY, SUNDAY -> this.with(next(MONDAY)) - else -> this - } - } - inline val LocalDate.weekDayName: String get() = this.format(ofPattern("EEEE", Locale.getDefault())) -inline val LocalDate.weekFirstDayAlwaysCurrent: LocalDate - get() = this.with(TemporalAdjusters.previousOrSame(MONDAY)) +inline val LocalDate.monday: LocalDate + get() = this.with(MONDAY) -inline val LocalDate.weekFirstDayNextOnWeekEnd: LocalDate - get() { - return when (this.dayOfWeek) { - SATURDAY, SUNDAY -> this.with(next(MONDAY)) - else -> this.with(previousOrSame(MONDAY)) - } - } +inline val LocalDate.friday: LocalDate + get() = this.with(FRIDAY) /** * [Dz.U. 2016 poz. 1335](http://prawo.sejm.gov.pl/isap.nsf/DocDetails.xsp?id=WDU20160001335) diff --git a/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt b/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt index 577409da5..6e64c2b7c 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/security/Scrambler.kt @@ -11,7 +11,7 @@ import android.security.KeyPairGeneratorSpec import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyProperties.* import android.util.Base64 -import android.util.Base64.DEFAULT +import android.util.Base64.* import timber.log.Timber import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream @@ -20,8 +20,8 @@ import java.nio.charset.Charset import java.security.KeyPairGenerator import java.security.KeyStore import java.security.PrivateKey -import java.security.PublicKey import java.util.* +import java.util.Calendar.YEAR import javax.crypto.Cipher import javax.crypto.Cipher.DECRYPT_MODE import javax.crypto.Cipher.ENCRYPT_MODE @@ -30,131 +30,114 @@ import javax.crypto.CipherOutputStream import javax.security.auth.x500.X500Principal import kotlin.collections.ArrayList -object Scrambler { +private const val KEY_ALIAS = "USER_PASSWORD" - private const val KEY_ALIAS = "USER_PASSWORD" +private const val ALGORITHM_RSA = "RSA" - private const val ALGORITHM_RSA = "RSA" +private const val KEYSTORE_NAME = "AndroidKeyStore" - private const val KEYSTORE_NAME = "AndroidKeyStore" +private const val KEY_TRANSFORMATION_ALGORITHM = "RSA/ECB/PKCS1Padding" - private const val KEY_TRANSFORMATION_ALGORITHM = "RSA/ECB/PKCS1Padding" +private const val KEY_CIPHER_JELLY_PROVIDER = "AndroidOpenSSL" - private const val KEY_CIPHER_JELLY_PROVIDER = "AndroidOpenSSL" +private const val KEY_CIPHER_M_PROVIDER = "AndroidKeyStoreBCWorkaround" - private const val KEY_CIPHER_M_PROVIDER = "AndroidKeyStoreBCWorkaround" +private val KEY_CHARSET = Charset.forName("UTF-8") - private val KEY_CHARSET = Charset.forName("UTF-8") +private val isKeyPairExists: Boolean + get() = keyStore.getKey(KEY_ALIAS, null) != null - @JvmStatic - fun encrypt(plainText: String, context: Context): String { - if (plainText.isEmpty()) throw ScramblerException("Text to be encrypted is empty") - - if (SDK_INT < JELLY_BEAN_MR2) { - return String(Base64.encode(plainText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET) - } - - return try { - if (!isKeyPairExist()) generateKeyPair(context) - - val cipher = getCipher() - cipher.init(ENCRYPT_MODE, getPublicKey()) - - val outputStream = ByteArrayOutputStream() - val cipherOutputStream = CipherOutputStream(outputStream, cipher) - cipherOutputStream.write(plainText.toByteArray(KEY_CHARSET)) - cipherOutputStream.close() - - Base64.encodeToString(outputStream.toByteArray(), DEFAULT) - } catch (exception: Exception) { - Timber.e(exception, "An error occurred while encrypting text") - String(Base64.encode(plainText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET) - } - - } - - @JvmStatic - fun decrypt(cipherText: String): String { - if (cipherText.isEmpty()) throw ScramblerException("Text to be encrypted is empty") - - if (SDK_INT < JELLY_BEAN_MR2 || cipherText.length < 250) { - return String(Base64.decode(cipherText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET) - } - - if (!isKeyPairExist()) throw ScramblerException("KeyPair doesn't exist") - - try { - val cipher = getCipher() - cipher.init(DECRYPT_MODE, getPrivateKey()) - - val input = CipherInputStream(ByteArrayInputStream(Base64.decode(cipherText, DEFAULT)), cipher) - val values = ArrayList() - - var nextByte = 0 - while ({ nextByte = input.read(); nextByte }() != -1) { - values.add(nextByte.toByte()) - } - - val bytes = ByteArray(values.size) - for (i in bytes.indices) { - bytes[i] = values[i] - } - return String(bytes, 0, bytes.size, KEY_CHARSET) - } catch (e: Exception) { - throw ScramblerException("An error occurred while decrypting text", e) - } - } - - private fun getKeyStoreInstance(): KeyStore { - val keyStore = KeyStore.getInstance(KEYSTORE_NAME) - keyStore.load(null) - return keyStore - } - - private fun getPublicKey(): PublicKey = - (getKeyStoreInstance().getEntry(KEY_ALIAS, null) as KeyStore.PrivateKeyEntry) - .certificate.publicKey - - - private fun getPrivateKey(): PrivateKey = - (getKeyStoreInstance().getEntry(KEY_ALIAS, null) as KeyStore.PrivateKeyEntry).privateKey - - - private fun getCipher(): Cipher { +private val cipher: Cipher + get() { return if (SDK_INT >= M) Cipher.getInstance(KEY_TRANSFORMATION_ALGORITHM, KEY_CIPHER_M_PROVIDER) else Cipher.getInstance(KEY_TRANSFORMATION_ALGORITHM, KEY_CIPHER_JELLY_PROVIDER) } - @TargetApi(JELLY_BEAN_MR2) - private fun generateKeyPair(context: Context) { - val spec = if (SDK_INT >= M) { - KeyGenParameterSpec.Builder(KEY_ALIAS, PURPOSE_DECRYPT or PURPOSE_ENCRYPT) - .setDigests(DIGEST_SHA256, DIGEST_SHA512) - .setCertificateSubject(X500Principal("CN=Wulkanowy")) - .setEncryptionPaddings(ENCRYPTION_PADDING_RSA_PKCS1) - .setSignaturePaddings(SIGNATURE_PADDING_RSA_PKCS1) - .setCertificateSerialNumber(BigInteger.TEN) - .build() - } else { - val start = Calendar.getInstance() - val end = Calendar.getInstance() - end.add(Calendar.YEAR, 99) +private val keyStore: KeyStore + get() = KeyStore.getInstance(KEYSTORE_NAME).apply { load(null) } - KeyPairGeneratorSpec.Builder(context) - .setAlias(KEY_ALIAS) - .setSubject(X500Principal("CN=Wulkanowy")) - .setSerialNumber(BigInteger.TEN) - .setStartDate(start.time) - .setEndDate(end.time) - .build() - } +fun encrypt(plainText: String, context: Context): String { + if (plainText.isEmpty()) throw ScramblerException("Text to be encrypted is empty") - val generator = KeyPairGenerator.getInstance(ALGORITHM_RSA, KEYSTORE_NAME) - generator.initialize(spec) - generator.generateKeyPair() - - Timber.i("A new KeyPair has been generated") + if (SDK_INT < JELLY_BEAN_MR2) { + return String(Base64.encode(plainText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET) + } + + return try { + if (!isKeyPairExists) generateKeyPair(context) + cipher.let { + it.init(ENCRYPT_MODE, keyStore.getCertificate(KEY_ALIAS).publicKey) + + ByteArrayOutputStream().let { output -> + CipherOutputStream(output, it).apply { + write(plainText.toByteArray(KEY_CHARSET)) + close() + } + encodeToString(output.toByteArray(), DEFAULT) + } + } + } catch (exception: Exception) { + Timber.e(exception, "An error occurred while encrypting text") + String(encode(plainText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET) } - private fun isKeyPairExist(): Boolean = getKeyStoreInstance().getKey(KEY_ALIAS, null) != null } + +fun decrypt(cipherText: String): String { + if (cipherText.isEmpty()) throw ScramblerException("Text to be encrypted is empty") + + if (SDK_INT < JELLY_BEAN_MR2 || cipherText.length < 250) { + return String(decode(cipherText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET) + } + + if (!isKeyPairExists) throw ScramblerException("KeyPair doesn't exist") + + return try { + cipher.let { + it.init(DECRYPT_MODE, (keyStore.getKey(KEY_ALIAS, null) as PrivateKey)) + + CipherInputStream(ByteArrayInputStream(decode(cipherText, DEFAULT)), it).let { input -> + val values = ArrayList() + var nextByte = 0 + while ({ nextByte = input.read(); nextByte }() != -1) { + values.add(nextByte.toByte()) + } + val bytes = ByteArray(values.size) + for (i in bytes.indices) { + bytes[i] = values[i] + } + String(bytes, 0, bytes.size, KEY_CHARSET) + } + } + } catch (e: Exception) { + throw ScramblerException("An error occurred while decrypting text", e) + } +} + +@TargetApi(JELLY_BEAN_MR2) +private fun generateKeyPair(context: Context) { + (if (SDK_INT >= M) { + KeyGenParameterSpec.Builder(KEY_ALIAS, PURPOSE_DECRYPT or PURPOSE_ENCRYPT) + .setDigests(DIGEST_SHA256, DIGEST_SHA512) + .setCertificateSubject(X500Principal("CN=Wulkanowy")) + .setEncryptionPaddings(ENCRYPTION_PADDING_RSA_PKCS1) + .setSignaturePaddings(SIGNATURE_PADDING_RSA_PKCS1) + .setCertificateSerialNumber(BigInteger.TEN) + .build() + } else { + KeyPairGeneratorSpec.Builder(context) + .setAlias(KEY_ALIAS) + .setSubject(X500Principal("CN=Wulkanowy")) + .setSerialNumber(BigInteger.TEN) + .setStartDate(Calendar.getInstance().time) + .setEndDate(Calendar.getInstance().apply { add(YEAR, 99) }.time) + .build() + }).let { + KeyPairGenerator.getInstance(ALGORITHM_RSA, KEYSTORE_NAME).apply { + initialize(it) + genKeyPair() + } + } + Timber.i("A new KeyPair has been generated") +} + diff --git a/app/src/main/res/layout/dialog_attendance.xml b/app/src/main/res/layout/dialog_attendance.xml index 8fd82d4d6..8e85ba1f1 100644 --- a/app/src/main/res/layout/dialog_attendance.xml +++ b/app/src/main/res/layout/dialog_attendance.xml @@ -4,17 +4,24 @@ android:layout_height="match_parent"> + + - diff --git a/app/src/main/res/layout/dialog_exam.xml b/app/src/main/res/layout/dialog_exam.xml index a15626600..6e69a13b3 100644 --- a/app/src/main/res/layout/dialog_exam.xml +++ b/app/src/main/res/layout/dialog_exam.xml @@ -4,18 +4,25 @@ android:layout_height="match_parent"> + + diff --git a/app/src/main/res/layout/dialog_timetable.xml b/app/src/main/res/layout/dialog_timetable.xml index a1f2b62f5..d0d4568d5 100644 --- a/app/src/main/res/layout/dialog_timetable.xml +++ b/app/src/main/res/layout/dialog_timetable.xml @@ -4,12 +4,20 @@ android:layout_height="match_parent"> + + diff --git a/app/src/main/res/layout/fragment_attendance.xml b/app/src/main/res/layout/fragment_attendance.xml index b91ee8242..1ce62294b 100644 --- a/app/src/main/res/layout/fragment_attendance.xml +++ b/app/src/main/res/layout/fragment_attendance.xml @@ -1,15 +1,13 @@ - + android:layout_height="match_parent"> - + android:layout_height="match_parent" + android:layout_marginBottom="50dp"> @@ -56,12 +54,14 @@ android:text="@string/attendance_no_items" android:textSize="20sp" /> - +