From f96d0ebed9cbee0e31e8d655a7ec89f5082efe40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Borcz?= Date: Fri, 7 Dec 2018 19:01:19 +0100 Subject: [PATCH] Add a summary of attendance (#132) --- app/build.gradle | 2 +- app/proguard-multidex-rules.pro | 2 +- .../github/wulkanowy/data/RepositoryModule.kt | 8 + .../github/wulkanowy/data/db/AppDatabase.kt | 12 +- .../io/github/wulkanowy/data/db/Converters.kt | 15 +- .../wulkanowy/data/db/dao/AttendanceDao.kt | 2 +- .../data/db/dao/AttendanceSummaryDao.kt | 21 ++ .../github/wulkanowy/data/db/dao/ExamDao.kt | 2 +- .../github/wulkanowy/data/db/dao/GradeDao.kt | 4 +- .../wulkanowy/data/db/dao/GradeSummaryDao.kt | 2 +- .../wulkanowy/data/db/dao/HomeworkDao.kt | 2 +- .../github/wulkanowy/data/db/dao/NoteDao.kt | 2 +- .../wulkanowy/data/db/dao/SemesterDao.kt | 2 +- .../wulkanowy/data/db/dao/SubjectDao.kt | 21 ++ .../wulkanowy/data/db/dao/TimetableDao.kt | 2 +- .../wulkanowy/data/db/entities/Attendance.kt | 28 +- .../data/db/entities/AttendanceSummary.kt | 43 +++ .../wulkanowy/data/db/entities/Subject.kt | 25 ++ .../data/repositories/AttendanceRepository.kt | 42 +-- .../AttendanceSummaryRepository.kt | 35 +++ .../repositories/GradeSummaryRepository.kt | 28 +- .../data/repositories/SubjectRepostory.kt | 38 +++ .../data/repositories/TimetableRepository.kt | 44 +-- .../repositories/local/AttendanceLocal.kt | 2 +- .../local/AttendanceSummaryLocal.kt | 22 ++ .../data/repositories/local/ExamLocal.kt | 2 +- .../data/repositories/local/GradeLocal.kt | 4 +- .../repositories/local/GradeSummaryLocal.kt | 2 +- .../data/repositories/local/HomeworkLocal.kt | 2 +- .../data/repositories/local/NoteLocal.kt | 2 +- .../data/repositories/local/SemesterLocal.kt | 2 +- .../data/repositories/local/SubjectLocal.kt | 25 ++ .../data/repositories/local/TimetableLocal.kt | 2 +- .../repositories/remote/AttendanceRemote.kt | 2 + .../remote/AttendanceSummaryRemote.kt | 33 +++ .../data/repositories/remote/SubjectRemote.kt | 27 ++ .../modules/attendance/AttendanceFragment.kt | 32 ++- .../modules/attendance/AttendancePresenter.kt | 19 +- .../ui/modules/attendance/AttendanceView.kt | 6 + .../summary/AttendanceSummaryFragment.kt | 120 +++++++++ .../summary/AttendanceSummaryItem.kt | 82 ++++++ .../summary/AttendanceSummaryPresenter.kt | 123 +++++++++ .../AttendanceSummaryScrollableHeader.kt | 46 ++++ .../summary/AttendanceSummaryView.kt | 26 ++ .../wulkanowy/ui/modules/exam/ExamFragment.kt | 2 +- .../ui/modules/exam/ExamPresenter.kt | 3 +- .../modules/grade/summary/GradeSummaryItem.kt | 16 +- .../wulkanowy/ui/modules/main/MainModule.kt | 5 + .../wulkanowy/ui/modules/more/MoreFragment.kt | 2 +- .../ui/modules/timetable/TimetableFragment.kt | 2 +- .../modules/timetable/TimetablePresenter.kt | 2 +- .../wulkanowy/utils/AttendanceExtension.kt | 26 ++ .../io/github/wulkanowy/utils/FabricUtils.kt | 3 +- .../wulkanowy/utils/SpinnerExtension.kt | 14 + .../github/wulkanowy/utils/TimeExtension.kt | 45 +++- ...e_about_24dp.xml => ic_all_about_24dp.xml} | 4 +- .../layout/fragment_attendance_summary.xml | 66 +++++ .../res/layout/item_attendance_summary.xml | 252 ++++++++++++++++++ .../item_attendance_summary_subject.xml | 14 + .../scrollable_header_attendance_summary.xml | 25 ++ .../main/res/menu/action_menu_attendance.xml | 10 + app/src/main/res/menu/action_menu_grade.xml | 2 +- app/src/main/res/values-pl/strings.xml | 2 + app/src/main/res/values/strings.xml | 3 + .../wulkanowy/utils/TimeExtensionTest.kt | 16 +- 65 files changed, 1351 insertions(+), 126 deletions(-) create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceSummaryDao.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/dao/SubjectDao.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/entities/AttendanceSummary.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/db/entities/Subject.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepostory.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/local/AttendanceSummaryLocal.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/local/SubjectLocal.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/remote/AttendanceSummaryRemote.kt create mode 100644 app/src/main/java/io/github/wulkanowy/data/repositories/remote/SubjectRemote.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryItem.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryScrollableHeader.kt create mode 100644 app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryView.kt create mode 100644 app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt create mode 100644 app/src/main/java/io/github/wulkanowy/utils/SpinnerExtension.kt rename app/src/main/res/drawable/{ic_more_about_24dp.xml => ic_all_about_24dp.xml} (83%) create mode 100644 app/src/main/res/layout/fragment_attendance_summary.xml create mode 100644 app/src/main/res/layout/item_attendance_summary.xml create mode 100644 app/src/main/res/layout/item_attendance_summary_subject.xml create mode 100644 app/src/main/res/layout/scrollable_header_attendance_summary.xml create mode 100644 app/src/main/res/menu/action_menu_attendance.xml diff --git a/app/build.gradle b/app/build.gradle index e16846db..f6557911 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -81,7 +81,7 @@ configurations.all { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" - implementation('com.github.wulkanowy:api:ba17abc') { exclude module: "threetenbp" } + implementation('com.github.wulkanowy:api:6a73b0e') { exclude module: "threetenbp" } implementation "androidx.legacy:legacy-support-v4:1.0.0" implementation "androidx.appcompat:appcompat:1.0.2" diff --git a/app/proguard-multidex-rules.pro b/app/proguard-multidex-rules.pro index 7c3895b6..9ee1737f 100644 --- a/app/proguard-multidex-rules.pro +++ b/app/proguard-multidex-rules.pro @@ -1,3 +1,3 @@ -keep class android.support.test.internal** { *; } -keep class org.junit.** { *; } --keep public class io.github.wulkanowy** { *; } \ No newline at end of file +-keep public class io.github.wulkanowy** { *; } diff --git a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt b/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt index 274b4d86..7f4b886f 100644 --- a/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt +++ b/app/src/main/java/io/github/wulkanowy/data/RepositoryModule.kt @@ -77,6 +77,10 @@ internal class RepositoryModule { @Provides fun provideAttendanceDao(database: AppDatabase) = database.attendanceDao + @Singleton + @Provides + fun provideAttendanceSummaryDao(database: AppDatabase) = database.attendanceSummaryDao + @Singleton @Provides fun provideTimetableDao(database: AppDatabase) = database.timetableDao @@ -88,4 +92,8 @@ internal class RepositoryModule { @Singleton @Provides fun provideHomeworkDao(database: AppDatabase) = database.homeworkDao + + @Singleton + @Provides + fun provideSubjectDao(database: AppDatabase) = database.subjectDao } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt index 640d30ed..30c957b4 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/AppDatabase.kt @@ -7,6 +7,7 @@ import androidx.room.RoomDatabase import androidx.room.RoomDatabase.JournalMode.TRUNCATE import androidx.room.TypeConverters import io.github.wulkanowy.data.db.dao.AttendanceDao +import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao import io.github.wulkanowy.data.db.dao.ExamDao import io.github.wulkanowy.data.db.dao.GradeDao import io.github.wulkanowy.data.db.dao.GradeSummaryDao @@ -15,8 +16,10 @@ import io.github.wulkanowy.data.db.dao.HomeworkDao import io.github.wulkanowy.data.db.dao.NoteDao import io.github.wulkanowy.data.db.dao.SemesterDao import io.github.wulkanowy.data.db.dao.StudentDao +import io.github.wulkanowy.data.db.dao.SubjectDao import io.github.wulkanowy.data.db.dao.TimetableDao import io.github.wulkanowy.data.db.entities.Attendance +import io.github.wulkanowy.data.db.entities.AttendanceSummary import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.GradeSummary @@ -25,6 +28,7 @@ import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Student +import io.github.wulkanowy.data.db.entities.Subject import io.github.wulkanowy.data.db.entities.Timetable import javax.inject.Singleton @@ -36,11 +40,13 @@ import javax.inject.Singleton Exam::class, Timetable::class, Attendance::class, + AttendanceSummary::class, Grade::class, GradeSummary::class, Message::class, Note::class, - Homework::class + Homework::class, + Subject::class ], version = 1, exportSchema = false @@ -66,6 +72,8 @@ abstract class AppDatabase : RoomDatabase() { abstract val attendanceDao: AttendanceDao + abstract val attendanceSummaryDao: AttendanceSummaryDao + abstract val gradeDao: GradeDao abstract val gradeSummaryDao: GradeSummaryDao @@ -75,4 +83,6 @@ abstract class AppDatabase : RoomDatabase() { abstract val noteDao: NoteDao abstract val homeworkDao: HomeworkDao + + abstract val subjectDao: SubjectDao } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/Converters.kt b/app/src/main/java/io/github/wulkanowy/data/db/Converters.kt index 0700dd43..a550df89 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/Converters.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/Converters.kt @@ -1,8 +1,13 @@ package io.github.wulkanowy.data.db import androidx.room.TypeConverter -import org.threeten.bp.* -import java.util.* +import org.threeten.bp.DateTimeUtils +import org.threeten.bp.Instant +import org.threeten.bp.LocalDate +import org.threeten.bp.LocalDateTime +import org.threeten.bp.Month +import org.threeten.bp.ZoneOffset +import java.util.Date class Converters { @@ -25,4 +30,10 @@ class Converters { fun timeToTimestamp(date: LocalDateTime?): Long? { return date?.atZone(ZoneOffset.UTC)?.toInstant()?.toEpochMilli() } + + @TypeConverter + fun monthToInt(month: Month?) = month?.value + + @TypeConverter + fun intToMonth(value: Int?) = value?.let { Month.of(it) } } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceDao.kt index 17a0bc74..d3c4f146 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceDao.kt @@ -20,5 +20,5 @@ interface AttendanceDao { fun deleteAll(exams: List) @Query("SELECT * FROM Attendance WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") - fun load(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe> + fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceSummaryDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceSummaryDao.kt new file mode 100644 index 00000000..a7413de5 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/AttendanceSummaryDao.kt @@ -0,0 +1,21 @@ +package io.github.wulkanowy.data.db.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.Query +import io.github.wulkanowy.data.db.entities.AttendanceSummary +import io.reactivex.Maybe + +@Dao +interface AttendanceSummaryDao { + + @Insert + fun insertAll(exams: List): List + + @Delete + fun deleteAll(exams: List) + + @Query("SELECT * FROM AttendanceSummary WHERE diary_id = :diaryId AND student_id = :studentId AND subject_id = :subjectId") + fun loadAll(diaryId: Int, studentId: Int, subjectId: Int): Maybe> +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/ExamDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/ExamDao.kt index fdd3eae2..06cd5613 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/ExamDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/ExamDao.kt @@ -20,5 +20,5 @@ interface ExamDao { fun deleteAll(exams: List) @Query("SELECT * FROM Exams WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") - fun load(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe> + fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDao.kt index 17011bd1..629f201d 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/GradeDao.kt @@ -26,8 +26,8 @@ interface GradeDao { fun deleteAll(grades: List) @Query("SELECT * FROM Grades WHERE semester_id = :semesterId AND student_id = :studentId") - fun load(semesterId: Int, studentId: Int): Maybe> + fun loadAll(semesterId: Int, studentId: Int): Maybe> @Query("SELECT * FROM Grades WHERE is_read = 0 AND semester_id = :semesterId AND student_id = :studentId") - fun loadNew(semesterId: Int, studentId: Int): Maybe> + fun loadAllNew(semesterId: Int, studentId: Int): Maybe> } 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 658ba7a8..3530118c 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 @@ -19,5 +19,5 @@ interface GradeSummaryDao { fun deleteAll(gradesSummary: List) @Query("SELECT * FROM grades_summary WHERE student_id = :studentId AND semester_id = :semesterId") - fun load(semesterId: Int, studentId: Int): Maybe> + fun loadAll(semesterId: Int, studentId: Int): Maybe> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/HomeworkDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/HomeworkDao.kt index bb4841db..4127460f 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/HomeworkDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/HomeworkDao.kt @@ -20,5 +20,5 @@ interface HomeworkDao { fun deleteAll(homework: List) @Query("SELECT * FROM Homework WHERE semester_id = :semesterId AND student_id = :studentId AND date = :date") - fun load(semesterId: Int, studentId: Int, date: LocalDate): Maybe> + fun loadAll(semesterId: Int, studentId: Int, date: LocalDate): Maybe> } diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/NoteDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/NoteDao.kt index 27decfbf..2c182860 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/NoteDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/NoteDao.kt @@ -26,7 +26,7 @@ interface NoteDao { fun deleteAll(notes: List) @Query("SELECT * FROM Notes WHERE semester_id = :semesterId AND student_id = :studentId") - fun load(semesterId: Int, studentId: Int): Maybe> + fun loadAll(semesterId: Int, studentId: Int): Maybe> @Query("SELECT * FROM Notes WHERE is_read = 0 AND semester_id = :semesterId AND student_id = :studentId") fun loadNew(semesterId: Int, studentId: Int): 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 7eef2d0e..44de31d8 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 @@ -16,7 +16,7 @@ interface SemesterDao { fun insertAll(semester: List) @Query("SELECT * FROM Semesters WHERE student_id = :studentId") - fun load(studentId: Int): Maybe> + fun loadAll(studentId: Int): Maybe> @Query("UPDATE Semesters SET is_current = 1 WHERE semester_id = :semesterId AND diary_id = :diaryId") fun updateCurrent(semesterId: Int, diaryId: Int) diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/SubjectDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/SubjectDao.kt new file mode 100644 index 00000000..725a371a --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/SubjectDao.kt @@ -0,0 +1,21 @@ +package io.github.wulkanowy.data.db.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.Query +import io.github.wulkanowy.data.db.entities.Subject +import io.reactivex.Maybe + +@Dao +interface SubjectDao { + + @Insert + fun insertAll(subjects: List): List + + @Delete + fun deleteAll(subjects: List) + + @Query("SELECT * FROM Subjects WHERE diary_id = :diaryId AND student_id = :studentId") + fun loadAll(diaryId: Int, studentId: Int): Maybe> +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt b/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt index 8c835c52..abe21361 100644 --- a/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt +++ b/app/src/main/java/io/github/wulkanowy/data/db/dao/TimetableDao.kt @@ -20,5 +20,5 @@ interface TimetableDao { fun deleteAll(exams: List) @Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") - fun load(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe> + fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe> } 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 bd571240..7588201b 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,31 +9,31 @@ import java.io.Serializable @Entity(tableName = "Attendance") data class Attendance( - @ColumnInfo(name = "student_id") - var studentId: Int, + @ColumnInfo(name = "student_id") + var studentId: Int, - @ColumnInfo(name = "diary_id") - var diaryId: Int, + @ColumnInfo(name = "diary_id") + var diaryId: Int, - var date: LocalDate, + var date: LocalDate, - var number: Int, + var number: Int, - var subject: String, + var subject: String, - var name: String, + var name: String, - var presence: Boolean = false, + var presence: Boolean = false, - var absence: Boolean = false, + var absence: Boolean = false, - var exemption: Boolean = false, + var exemption: Boolean = false, - var lateness: Boolean = false, + var lateness: Boolean = false, - var excused: Boolean = false, + var excused: Boolean = false, - var deleted: Boolean = false + var deleted: Boolean = false ) : Serializable { @PrimaryKey(autoGenerate = true) diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/AttendanceSummary.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/AttendanceSummary.kt new file mode 100644 index 00000000..de2de98f --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/AttendanceSummary.kt @@ -0,0 +1,43 @@ +package io.github.wulkanowy.data.db.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import org.threeten.bp.Month +import java.io.Serializable + +@Entity(tableName = "AttendanceSummary") +data class AttendanceSummary( + + @ColumnInfo(name = "student_id") + var studentId: Int, + + @ColumnInfo(name = "diary_id") + var diaryId: Int, + + @ColumnInfo(name = "subject_id") + var subjectId: Int = 0, + + val month: Month, + + val presence: Int, + + val absence: Int, + + @ColumnInfo(name = "absence_excused") + val absenceExcused: Int, + + @ColumnInfo(name = "absence_for_school_reasons") + val absenceForSchoolReasons: Int, + + val lateness: Int, + + @ColumnInfo(name = "lateness_excused") + val latenessExcused: Int, + + val exemption: Int +) : Serializable { + + @PrimaryKey(autoGenerate = true) + var id: Long = 0 +} diff --git a/app/src/main/java/io/github/wulkanowy/data/db/entities/Subject.kt b/app/src/main/java/io/github/wulkanowy/data/db/entities/Subject.kt new file mode 100644 index 00000000..45306be3 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/db/entities/Subject.kt @@ -0,0 +1,25 @@ +package io.github.wulkanowy.data.db.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.io.Serializable + +@Entity(tableName = "Subjects") +data class Subject( + + @ColumnInfo(name = "student_id") + var studentId: Int, + + @ColumnInfo(name = "diary_id") + var diaryId: Int, + + @ColumnInfo(name = "real_id") + var realId: Int, + + var name: String +) : 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 ce62cafa..eb7230ae 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 @@ -16,30 +16,30 @@ import javax.inject.Singleton @Singleton class AttendanceRepository @Inject constructor( - private val settings: InternetObservingSettings, - private val local: AttendanceLocal, - private val remote: AttendanceRemote + private val settings: InternetObservingSettings, + private val local: AttendanceLocal, + private val remote: AttendanceRemote ) { fun getAttendance(semester: Semester, startDate: LocalDate, endDate: LocalDate, forceRefresh: Boolean) - : Single> { + : 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 } } - } + .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/AttendanceSummaryRepository.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt new file mode 100644 index 00000000..8e21b12c --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/AttendanceSummaryRepository.kt @@ -0,0 +1,35 @@ +package io.github.wulkanowy.data.repositories + +import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork +import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings +import io.github.wulkanowy.data.db.entities.AttendanceSummary +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.repositories.local.AttendanceSummaryLocal +import io.github.wulkanowy.data.repositories.remote.AttendanceSummaryRemote +import io.reactivex.Single +import java.net.UnknownHostException +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AttendanceSummaryRepository @Inject constructor( + private val settings: InternetObservingSettings, + private val local: AttendanceSummaryLocal, + private val remote: AttendanceSummaryRemote +) { + + fun getAttendanceSummary(semester: Semester, subjectId: Int, forceRefresh: Boolean = false): Single>? { + return local.getAttendanceSummary(semester, subjectId).filter { !forceRefresh } + .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) + .flatMap { + if (it) remote.getAttendanceSummary(semester, subjectId) + else Single.error(UnknownHostException()) + }.flatMap { new -> + local.getAttendanceSummary(semester, subjectId).toSingle(emptyList()) + .doOnSuccess { old -> + local.deleteAttendanceSummary(old - new) + local.saveAttendanceSummary(new - old) + } + }.flatMap { local.getAttendanceSummary(semester, subjectId).toSingle(emptyList()) }) + } +} 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 e809ff59..a59a2cd5 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 @@ -13,23 +13,23 @@ import javax.inject.Singleton @Singleton class GradeSummaryRepository @Inject constructor( - private val settings: InternetObservingSettings, - private val local: GradeSummaryLocal, - private val remote: GradeSummaryRemote + private val settings: InternetObservingSettings, + private val local: GradeSummaryLocal, + private val remote: GradeSummaryRemote ) { fun getGradesSummary(semester: Semester, forceRefresh: Boolean = false): Single> { return local.getGradesSummary(semester).filter { !forceRefresh } - .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) - .flatMap { - if (it) remote.getGradeSummary(semester) - else Single.error(UnknownHostException()) - }.flatMap { newGradesSummary -> - local.getGradesSummary(semester).toSingle(emptyList()) - .doOnSuccess { oldGradesSummary -> - local.deleteGradesSummary(oldGradesSummary - newGradesSummary) - local.saveGradesSummary(newGradesSummary - oldGradesSummary) - } - }.flatMap { local.getGradesSummary(semester).toSingle(emptyList()) }) + .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) + .flatMap { + if (it) remote.getGradeSummary(semester) + else Single.error(UnknownHostException()) + }.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/SubjectRepostory.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepostory.kt new file mode 100644 index 00000000..e3e8a2a7 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/SubjectRepostory.kt @@ -0,0 +1,38 @@ +package io.github.wulkanowy.data.repositories + +import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork +import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Subject +import io.github.wulkanowy.data.repositories.local.SubjectLocal +import io.github.wulkanowy.data.repositories.remote.SubjectRemote +import io.reactivex.Single +import java.net.UnknownHostException +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SubjectRepostory @Inject constructor( + private val settings: InternetObservingSettings, + private val local: SubjectLocal, + private val remote: SubjectRemote +) { + + fun getSubjects(semester: Semester, forceRefresh: Boolean = false): Single> { + return local.getSubjects(semester).filter { !forceRefresh } + .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) + .flatMap { + if (it) remote.getSubjects(semester) + else Single.error(UnknownHostException()) + }.flatMap { new -> + local.getSubjects(semester) + .toSingle(emptyList()) + .doOnSuccess { old -> + local.deleteSubjects(old - new) + local.saveSubjects(new - old) + } + }.flatMap { + local.getSubjects(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 95a1563f..6cc6a043 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 @@ -16,31 +16,31 @@ import javax.inject.Singleton @Singleton class TimetableRepository @Inject constructor( - private val settings: InternetObservingSettings, - private val local: TimetableLocal, - private val remote: TimetableRemote + private val settings: InternetObservingSettings, + private val local: TimetableLocal, + private val remote: TimetableRemote ) { fun getTimetable(semester: Semester, startDate: LocalDate, endDate: LocalDate, forceRefresh: Boolean = false) - : Single> { + : 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 } } - } + .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/AttendanceLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/local/AttendanceLocal.kt index a95ad741..9a318dba 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/local/AttendanceLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/local/AttendanceLocal.kt @@ -10,7 +10,7 @@ import javax.inject.Inject class AttendanceLocal @Inject constructor(private val attendanceDb: AttendanceDao) { fun getAttendance(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe> { - return attendanceDb.load(semester.diaryId, semester.studentId, startDate, endDate) + return attendanceDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate) .filter { !it.isEmpty() } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/local/AttendanceSummaryLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/local/AttendanceSummaryLocal.kt new file mode 100644 index 00000000..2bb9f122 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/local/AttendanceSummaryLocal.kt @@ -0,0 +1,22 @@ +package io.github.wulkanowy.data.repositories.local + +import io.github.wulkanowy.data.db.dao.AttendanceSummaryDao +import io.github.wulkanowy.data.db.entities.AttendanceSummary +import io.github.wulkanowy.data.db.entities.Semester +import io.reactivex.Maybe +import javax.inject.Inject + +class AttendanceSummaryLocal @Inject constructor(private val attendanceDb: AttendanceSummaryDao) { + + fun getAttendanceSummary(semester: Semester, subjectId: Int): Maybe> { + return attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId).filter { !it.isEmpty() } + } + + fun saveAttendanceSummary(attendance: List) { + attendanceDb.insertAll(attendance) + } + + fun deleteAttendanceSummary(attendance: List) { + attendanceDb.deleteAll(attendance) + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/local/ExamLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/local/ExamLocal.kt index 40a3e8e7..3e32a635 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/local/ExamLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/local/ExamLocal.kt @@ -10,7 +10,7 @@ import javax.inject.Inject class ExamLocal @Inject constructor(private val examDb: ExamDao) { fun getExams(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe> { - return examDb.load(semester.diaryId, semester.studentId, startDate, endDate) + return examDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate) .filter { !it.isEmpty() } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/local/GradeLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/local/GradeLocal.kt index b5d0d345..d6d46340 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/local/GradeLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/local/GradeLocal.kt @@ -12,11 +12,11 @@ import javax.inject.Singleton class GradeLocal @Inject constructor(private val gradeDb: GradeDao) { fun getGrades(semester: Semester): Maybe> { - return gradeDb.load(semester.semesterId, semester.studentId).filter { !it.isEmpty() } + return gradeDb.loadAll(semester.semesterId, semester.studentId).filter { !it.isEmpty() } } fun getNewGrades(semester: Semester): Maybe> { - return gradeDb.loadNew(semester.semesterId, semester.studentId) + return gradeDb.loadAllNew(semester.semesterId, semester.studentId) } fun saveGrades(grades: List) { 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 0a7f4679..6a72416d 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 @@ -11,7 +11,7 @@ import javax.inject.Singleton class GradeSummaryLocal @Inject constructor(private val gradeSummaryDb: GradeSummaryDao) { fun getGradesSummary(semester: Semester): Maybe> { - return gradeSummaryDb.load(semester.semesterId, semester.studentId) + return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId) .filter { !it.isEmpty() } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/local/HomeworkLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/local/HomeworkLocal.kt index ea3818dd..ea18d814 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/local/HomeworkLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/local/HomeworkLocal.kt @@ -12,7 +12,7 @@ import javax.inject.Singleton class HomeworkLocal @Inject constructor(private val homeworkDb: HomeworkDao) { fun getHomework(semester: Semester, date: LocalDate): Maybe> { - return homeworkDb.load(semester.semesterId, semester.studentId, date).filter { !it.isEmpty() } + return homeworkDb.loadAll(semester.semesterId, semester.studentId, date).filter { !it.isEmpty() } } fun saveHomework(homework: List) { diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/local/NoteLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/local/NoteLocal.kt index ef4ea987..543eab9b 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/local/NoteLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/local/NoteLocal.kt @@ -12,7 +12,7 @@ import javax.inject.Singleton class NoteLocal @Inject constructor(private val noteDb: NoteDao) { fun getNotes(semester: Semester): Maybe> { - return noteDb.load(semester.semesterId, semester.studentId).filter { !it.isEmpty() } + return noteDb.loadAll(semester.semesterId, semester.studentId).filter { !it.isEmpty() } } fun getNewNotes(semester: Semester): Maybe> { diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/local/SemesterLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/local/SemesterLocal.kt index 0e084d91..77000478 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/local/SemesterLocal.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/local/SemesterLocal.kt @@ -15,7 +15,7 @@ class SemesterLocal @Inject constructor(private val semesterDb: SemesterDao) { } fun getSemesters(student: Student): Maybe> { - return semesterDb.load(student.studentId).filter { !it.isEmpty() } + return semesterDb.loadAll(student.studentId).filter { !it.isEmpty() } } fun setCurrentSemester(semester: Semester) { diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/local/SubjectLocal.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/local/SubjectLocal.kt new file mode 100644 index 00000000..7ca7c1b0 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/local/SubjectLocal.kt @@ -0,0 +1,25 @@ +package io.github.wulkanowy.data.repositories.local + +import io.github.wulkanowy.data.db.dao.SubjectDao +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Subject +import io.reactivex.Maybe +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SubjectLocal @Inject constructor(private val subjectDao: SubjectDao) { + + fun getSubjects(semester: Semester): Maybe> { + return subjectDao.loadAll(semester.diaryId, semester.studentId) + .filter { !it.isEmpty() } + } + + fun saveSubjects(subjects: List) { + subjectDao.insertAll(subjects) + } + + fun deleteSubjects(subjects: List) { + subjectDao.deleteAll(subjects) + } +} 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 21f9dbd2..63cc7c94 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 @@ -10,7 +10,7 @@ import javax.inject.Inject class TimetableLocal @Inject constructor(private val timetableDb: TimetableDao) { fun getTimetable(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe> { - return timetableDb.load(semester.diaryId, semester.studentId, startDate, endDate) + return timetableDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate) .filter { !it.isEmpty() } } diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/remote/AttendanceRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/remote/AttendanceRemote.kt index 8ce3ceda..79047606 100644 --- a/app/src/main/java/io/github/wulkanowy/data/repositories/remote/AttendanceRemote.kt +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/remote/AttendanceRemote.kt @@ -7,7 +7,9 @@ import io.github.wulkanowy.utils.toLocalDate import io.reactivex.Single import org.threeten.bp.LocalDate import javax.inject.Inject +import javax.inject.Singleton +@Singleton class AttendanceRemote @Inject constructor(private val api: Api) { fun getAttendance(semester: Semester, startDate: LocalDate, endDate: LocalDate): Single> { diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/remote/AttendanceSummaryRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/remote/AttendanceSummaryRemote.kt new file mode 100644 index 00000000..079eb4b8 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/remote/AttendanceSummaryRemote.kt @@ -0,0 +1,33 @@ +package io.github.wulkanowy.data.repositories.remote + +import io.github.wulkanowy.api.Api +import io.github.wulkanowy.data.db.entities.AttendanceSummary +import io.github.wulkanowy.data.db.entities.Semester +import io.reactivex.Single +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AttendanceSummaryRemote @Inject constructor(private val api: Api) { + + fun getAttendanceSummary(semester: Semester, subjectId: Int): Single> { + return Single.just(api.apply { diaryId = semester.diaryId }) + .flatMap { api.getAttendanceSummary(subjectId) }.map { attendance -> + attendance.map { + AttendanceSummary( + studentId = semester.studentId, + diaryId = semester.diaryId, + subjectId = subjectId, + month = it.month, + presence = it.presence, + absence = it.absence, + absenceExcused = it.absenceExcused, + absenceForSchoolReasons = it.absenceForSchoolReasons, + lateness = it.lateness, + latenessExcused = it.latenessExcused, + exemption = it.exemption + ) + } + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/data/repositories/remote/SubjectRemote.kt b/app/src/main/java/io/github/wulkanowy/data/repositories/remote/SubjectRemote.kt new file mode 100644 index 00000000..ae565d48 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/data/repositories/remote/SubjectRemote.kt @@ -0,0 +1,27 @@ +package io.github.wulkanowy.data.repositories.remote + +import io.github.wulkanowy.api.Api +import io.github.wulkanowy.data.db.entities.Semester +import io.github.wulkanowy.data.db.entities.Subject +import io.reactivex.Single +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SubjectRemote @Inject constructor(private val api: Api) { + + fun getSubjects(semester: Semester): Single> { + return Single.just(api.apply { diaryId = semester.diaryId }) + .flatMap { api.getSubjects() } + .map { subjects -> + subjects.map { + Subject( + studentId = semester.studentId, + diaryId = semester.diaryId, + name = it.name, + realId = it.value + ) + } + } + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt index bd582a3b..765c0ee6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceFragment.kt @@ -2,6 +2,9 @@ package io.github.wulkanowy.ui.modules.attendance import android.os.Bundle import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem import android.view.View import android.view.ViewGroup import eu.davidea.flexibleadapter.FlexibleAdapter @@ -10,6 +13,8 @@ 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.modules.attendance.summary.AttendanceSummaryFragment +import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.utils.setOnItemClickListener import kotlinx.android.synthetic.main.fragment_attendance.* @@ -35,6 +40,14 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie override val isViewEmpty: Boolean get() = attendanceAdapter.isEmpty + override val currentStackSize: Int? + get() = (activity as? MainActivity)?.currentStackSize + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_attendance, container, false) } @@ -59,6 +72,15 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie attendanceNextButton.setOnClickListener { presenter.onNextDay() } } + override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) { + inflater?.inflate(R.menu.action_menu_attendance, menu) + } + + override fun onOptionsItemSelected(item: MenuItem?): Boolean { + return if (item?.itemId == R.id.attendanceMenuSummary) presenter.onSummarySwitchSelected() + else false + } + override fun updateData(data: List) { attendanceAdapter.updateDataSet(data, true) } @@ -72,13 +94,17 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie } override fun resetView() { - attendanceAdapter.smoothScrollToPosition(0) + attendanceRecycler.smoothScrollToPosition(0) } override fun onFragmentReselected() { presenter.onViewReselected() } + override fun popView() { + (activity as? MainActivity)?.popView() + } + override fun showEmpty(show: Boolean) { attendanceEmpty.visibility = if (show) View.VISIBLE else View.GONE } @@ -107,6 +133,10 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie AttendanceDialog.newInstance(lesson).show(fragmentManager, lesson.toString()) } + override fun openSummaryView() { + (activity as? MainActivity)?.pushView(AttendanceSummaryFragment.newInstance()) + } + override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay()) diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt index f1e47093..74e585d4 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendancePresenter.kt @@ -56,11 +56,15 @@ class AttendancePresenter @Inject constructor( } fun onViewReselected() { - now().previousOrSameSchoolDay.also { - if (currentDate != it) { - loadData(it) - reloadView() - } else view?.resetView() + view?.also { view -> + if (view.currentStackSize == 1) { + now().previousOrSameSchoolDay.also { + if (currentDate != it) { + loadData(it) + reloadView() + } else if (!view.isViewEmpty) view.resetView() + } + } else view.popView() } } @@ -68,6 +72,11 @@ class AttendancePresenter @Inject constructor( if (item is AttendanceItem) view?.showAttendanceDialog(item.attendance) } + fun onSummarySwitchSelected(): Boolean { + view?.openSummaryView() + return true + } + private fun loadData(date: LocalDate, forceRefresh: Boolean = false) { currentDate = date disposable.apply { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt index 4957b5f0..b4ea981a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/AttendanceView.kt @@ -7,6 +7,8 @@ interface AttendanceView : BaseView { val isViewEmpty: Boolean + val currentStackSize: Int? + fun initView() fun updateData(data: List) @@ -30,4 +32,8 @@ interface AttendanceView : BaseView { fun showNextButton(show: Boolean) fun showAttendanceDialog(lesson: Attendance) + + fun openSummaryView() + + fun popView() } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt new file mode 100644 index 00000000..eee9e834 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryFragment.kt @@ -0,0 +1,120 @@ +package io.github.wulkanowy.ui.modules.attendance.summary + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE +import android.view.ViewGroup +import android.widget.ArrayAdapter +import android.widget.TextView +import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager +import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import io.github.wulkanowy.R +import io.github.wulkanowy.ui.base.BaseFragment +import io.github.wulkanowy.ui.modules.main.MainView +import io.github.wulkanowy.utils.setOnItemSelectedListener +import kotlinx.android.synthetic.main.fragment_attendance_summary.* +import javax.inject.Inject + +class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainView.TitledView { + + @Inject + lateinit var presenter: AttendanceSummaryPresenter + + @Inject + lateinit var attendanceSummaryAdapter: FlexibleAdapter> + + private lateinit var subjectsAdapter: ArrayAdapter + + companion object { + private const val SAVED_SUBJECT_KEY = "CURRENT_SUBJECT" + + fun newInstance() = AttendanceSummaryFragment() + } + + override val titleStringId: Int + get() = R.string.attendance_title + + override val isViewEmpty + get() = attendanceSummaryAdapter.isEmpty + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_attendance_summary, container, false) + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + messageContainer = attendanceSummaryRecycler + presenter.onAttachView(this, savedInstanceState?.getInt(SAVED_SUBJECT_KEY)) + } + + override fun initView() { + attendanceSummaryRecycler.run { + layoutManager = SmoothScrollLinearLayoutManager(context) + adapter = attendanceSummaryAdapter + } + attendanceSummarySwipe.setOnRefreshListener { presenter.onSwipeRefresh() } + + context?.let { + subjectsAdapter = ArrayAdapter(it, android.R.layout.simple_spinner_item, ArrayList()) + subjectsAdapter.setDropDownViewResource(R.layout.item_attendance_summary_subject) + } + + attendanceSummarySubjects.run { + adapter = subjectsAdapter + setOnItemSelectedListener { presenter.onSubjectSelected((it as TextView).text.toString()) } + } + } + + override fun updateSubjects(data: ArrayList) { + subjectsAdapter.run { + clear() + addAll(data) + notifyDataSetChanged() + } + } + + override fun updateDataSet(data: List, header: AttendanceSummaryScrollableHeader) { + attendanceSummaryAdapter.apply { + updateDataSet(data, true) + removeAllScrollableHeaders() + addScrollableHeader(header) + } + } + + override fun clearView() { + attendanceSummaryAdapter.clear() + } + + override fun showEmpty(show: Boolean) { + attendanceSummaryEmpty.visibility = if (show) VISIBLE else GONE + } + + override fun showProgress(show: Boolean) { + attendanceSummaryProgress.visibility = if (show) VISIBLE else GONE + } + + override fun showContent(show: Boolean) { + attendanceSummaryRecycler.visibility = if (show) VISIBLE else GONE + } + + override fun showSubjects(show: Boolean) { + attendanceSummarySubjects.visibility = if (show) VISIBLE else VISIBLE + } + + override fun hideRefresh() { + attendanceSummarySwipe.isRefreshing = false + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putInt(SAVED_SUBJECT_KEY, presenter.currentSubjectId) + } + + override fun onDestroyView() { + super.onDestroyView() + presenter.onDetachView() + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryItem.kt new file mode 100644 index 00000000..3102ce11 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryItem.kt @@ -0,0 +1,82 @@ +package io.github.wulkanowy.ui.modules.attendance.summary + +import android.view.View +import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import eu.davidea.flexibleadapter.items.IFlexible +import eu.davidea.viewholders.FlexibleViewHolder +import io.github.wulkanowy.R +import kotlinx.android.extensions.LayoutContainer +import kotlinx.android.synthetic.main.item_attendance_summary.* + +class AttendanceSummaryItem( + private val month: String, + private val percentage: String, + private val present: String, + private val absence: String, + private val excusedAbsence: String, + private val schoolAbsence: String, + private val exemption: String, + private val lateness: String, + private val excusedLateness: String +) : AbstractFlexibleItem() { + + override fun getLayoutRes() = R.layout.item_attendance_summary + + override fun createViewHolder(view: View?, adapter: FlexibleAdapter>?): ViewHolder { + return ViewHolder(view, adapter) + } + + override fun bindViewHolder(adapter: FlexibleAdapter>?, holder: ViewHolder?, position: Int, payloads: MutableList?) { + holder?.apply { + attendanceSummaryMonth.text = month + attendanceSummaryPercentage.text = percentage + attendanceSummaryPresent.text = present + attendanceSummaryAbsenceUnexcused.text = absence + attendanceSummaryAbsenceExcused.text = excusedAbsence + attendanceSummaryAbsenceSchool.text = schoolAbsence + attendanceSummaryExemption.text = exemption + attendanceSummaryLatenessUnexcused.text = lateness + attendanceSummaryLatenessExcused.text = excusedLateness + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as AttendanceSummaryItem + + if (month != other.month) return false + if (percentage != other.percentage) return false + if (present != other.present) return false + if (absence != other.absence) return false + if (excusedAbsence != other.excusedAbsence) return false + if (schoolAbsence != other.schoolAbsence) return false + if (exemption != other.exemption) return false + if (lateness != other.lateness) return false + if (excusedLateness != other.excusedLateness) return false + + return true + } + + override fun hashCode(): Int { + var result = month.hashCode() + result = 31 * result + percentage.hashCode() + result = 31 * result + present.hashCode() + result = 31 * result + absence.hashCode() + result = 31 * result + excusedAbsence.hashCode() + result = 31 * result + schoolAbsence.hashCode() + result = 31 * result + exemption.hashCode() + result = 31 * result + lateness.hashCode() + result = 31 * result + excusedLateness.hashCode() + return result + } + + class ViewHolder(view: View?, adapter: FlexibleAdapter>?) : FlexibleViewHolder(view, adapter), + LayoutContainer { + + override val containerView: View? + get() = contentView + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt new file mode 100644 index 00000000..931d3263 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryPresenter.kt @@ -0,0 +1,123 @@ +package io.github.wulkanowy.ui.modules.attendance.summary + +import io.github.wulkanowy.data.ErrorHandler +import io.github.wulkanowy.data.db.entities.AttendanceSummary +import io.github.wulkanowy.data.db.entities.Subject +import io.github.wulkanowy.data.repositories.AttendanceSummaryRepository +import io.github.wulkanowy.data.repositories.SemesterRepository +import io.github.wulkanowy.data.repositories.StudentRepository +import io.github.wulkanowy.data.repositories.SubjectRepostory +import io.github.wulkanowy.ui.base.BasePresenter +import io.github.wulkanowy.utils.SchedulersProvider +import io.github.wulkanowy.utils.calculatePercentage +import io.github.wulkanowy.utils.getFormattedName +import io.github.wulkanowy.utils.logEvent +import java.lang.String.format +import java.util.Locale.FRANCE +import java.util.concurrent.TimeUnit.MILLISECONDS +import javax.inject.Inject + +class AttendanceSummaryPresenter @Inject constructor( + private val errorHandler: ErrorHandler, + private val attendanceSummaryRepository: AttendanceSummaryRepository, + private val subjectRepository: SubjectRepostory, + private val studentRepository: StudentRepository, + private val semesterRepository: SemesterRepository, + private val schedulers: SchedulersProvider +) : BasePresenter(errorHandler) { + + private var subjects = emptyList() + + var currentSubjectId = -1 + private set + + fun onAttachView(view: AttendanceSummaryView, subjectId: Int?) { + super.onAttachView(view) + view.initView() + loadData(subjectId ?: -1) + loadSubjects() + } + + fun onSwipeRefresh() { + loadData(currentSubjectId, true) + } + + fun onSubjectSelected(name: String) { + view?.run { + showContent(false) + showProgress(true) + clearView() + } + loadData(subjects.singleOrNull { it.name == name }?.realId ?: -1) + } + + private fun loadData(subjectId: Int, forceRefresh: Boolean = false) { + currentSubjectId = subjectId + disposable.apply { + clear() + add(studentRepository.getCurrentStudent() + .delay(200, MILLISECONDS) + .flatMap { semesterRepository.getCurrentSemester(it) } + .flatMap { attendanceSummaryRepository.getAttendanceSummary(it, subjectId, forceRefresh) } + .map { createAttendanceSummaryItems(it) to AttendanceSummaryScrollableHeader(formatPercentage(it.calculatePercentage())) } + .subscribeOn(schedulers.backgroundThread) + .observeOn(schedulers.mainThread) + .doFinally { + view?.run { + hideRefresh() + showProgress(false) + } + } + .subscribe({ + view?.apply { + showEmpty(it.first.isEmpty()) + showContent(it.first.isNotEmpty()) + updateDataSet(it.first, it.second) + } + logEvent("Attendance load", mapOf("forceRefresh" to forceRefresh)) + }) { + view?.run { showEmpty(isViewEmpty) } + errorHandler.dispatch(it) + } + ) + } + } + + private fun loadSubjects() { + disposable.add(studentRepository.getCurrentStudent() + .flatMap { semesterRepository.getCurrentSemester(it) } + .flatMap { subjectRepository.getSubjects(it) } + .doOnSuccess { subjects = it } + .map { ArrayList(it.map { subject -> subject.name }) } + .subscribeOn(schedulers.backgroundThread) + .observeOn(schedulers.mainThread) + .subscribe({ + view?.run { + view?.updateSubjects(it) + showSubjects(true) + } + }, { errorHandler.dispatch(it) }) + ) + } + + private fun createAttendanceSummaryItems(attendanceSummary: List): List { + return attendanceSummary.sortedByDescending { it.id }.map { + AttendanceSummaryItem( + month = it.month.getFormattedName(), + percentage = formatPercentage(it.calculatePercentage()), + present = it.presence.toString(), + absence = it.absence.toString(), + excusedAbsence = it.absenceExcused.toString(), + schoolAbsence = it.absenceForSchoolReasons.toString(), + exemption = it.exemption.toString(), + lateness = it.lateness.toString(), + excusedLateness = it.latenessExcused.toString() + ) + } + } + + private fun formatPercentage(percentage: Double): String { + return if (percentage == 0.0) "0%" + else "${format(FRANCE, "%.2f", percentage)}%" + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryScrollableHeader.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryScrollableHeader.kt new file mode 100644 index 00000000..c258f71d --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryScrollableHeader.kt @@ -0,0 +1,46 @@ +package io.github.wulkanowy.ui.modules.attendance.summary + +import android.view.View +import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.items.AbstractFlexibleItem +import eu.davidea.flexibleadapter.items.IFlexible +import eu.davidea.viewholders.FlexibleViewHolder +import io.github.wulkanowy.R +import kotlinx.android.extensions.LayoutContainer +import kotlinx.android.synthetic.main.scrollable_header_attendance_summary.* + +class AttendanceSummaryScrollableHeader(private val percentage: String) : + AbstractFlexibleItem() { + + override fun getLayoutRes() = R.layout.scrollable_header_attendance_summary + + override fun createViewHolder(view: View?, adapter: FlexibleAdapter>?): ViewHolder { + return ViewHolder(view, adapter) + } + + override fun bindViewHolder(adapter: FlexibleAdapter>?, holder: ViewHolder?, position: Int, payloads: MutableList?) { + holder?.apply { attendanceSummaryScrollableHeaderPercentage.text = percentage } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as AttendanceSummaryScrollableHeader + + if (percentage != other.percentage) return false + + return true + } + + override fun hashCode(): Int { + return percentage.hashCode() + } + + class ViewHolder(view: View?, adapter: FlexibleAdapter>?) : FlexibleViewHolder(view, adapter), + LayoutContainer { + + override val containerView: View? + get() = contentView + } +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryView.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryView.kt new file mode 100644 index 00000000..03419a63 --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/attendance/summary/AttendanceSummaryView.kt @@ -0,0 +1,26 @@ +package io.github.wulkanowy.ui.modules.attendance.summary + +import io.github.wulkanowy.ui.base.BaseView + +interface AttendanceSummaryView : BaseView { + + val isViewEmpty: Boolean + + fun initView() + + fun hideRefresh() + + fun showContent(show: Boolean) + + fun showProgress(show: Boolean) + + fun showEmpty(show: Boolean) + + fun updateDataSet(data: List, header: AttendanceSummaryScrollableHeader) + + fun updateSubjects(data: ArrayList) + + fun showSubjects(show: Boolean) + + fun clearView() +} diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt index af488af4..c23ad22b 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamFragment.kt @@ -78,7 +78,7 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView. } override fun resetView() { - examAdapter.smoothScrollToPosition(0) + examRecycler.scrollToPosition(0) } override fun onFragmentReselected() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt index 7d241103..fbf9712a 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/exam/ExamPresenter.kt @@ -63,8 +63,7 @@ class ExamPresenter @Inject constructor( if (currentDate != it) { loadData(it) reloadView() - view?.resetView() - } else view?.resetView() + } else if (view?.isViewEmpty == false) view?.resetView() } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryItem.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryItem.kt index 98e5db92..54302fa6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryItem.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/grade/summary/GradeSummaryItem.kt @@ -6,11 +6,12 @@ import eu.davidea.flexibleadapter.items.AbstractSectionableItem import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.viewholders.FlexibleViewHolder import io.github.wulkanowy.R +import io.github.wulkanowy.data.db.entities.GradeSummary import kotlinx.android.extensions.LayoutContainer import kotlinx.android.synthetic.main.item_grade_summary.* -class GradeSummaryItem(header: GradeSummaryHeader, private val grade: String, private val title: String) - : AbstractSectionableItem(header) { +class GradeSummaryItem(header: GradeSummaryHeader, private val grade: String, private val title: String) : + AbstractSectionableItem(header) { override fun getLayoutRes() = R.layout.item_grade_summary @@ -18,8 +19,10 @@ class GradeSummaryItem(header: GradeSummaryHeader, private val grade: String, pr return ViewHolder(view, adapter) } - override fun bindViewHolder(adapter: FlexibleAdapter>?, holder: ViewHolder?, - position: Int, payloads: MutableList?) { + override fun bindViewHolder( + adapter: FlexibleAdapter>?, holder: ViewHolder?, + position: Int, payloads: MutableList? + ) { holder?.run { gradeSummaryItemGrade.text = grade gradeSummaryItemTitle.text = title @@ -46,9 +49,8 @@ class GradeSummaryItem(header: GradeSummaryHeader, private val grade: String, pr return result } - - class ViewHolder(view: View?, adapter: FlexibleAdapter>?) - : FlexibleViewHolder(view, adapter), LayoutContainer { + class ViewHolder(view: View?, adapter: FlexibleAdapter>?) : FlexibleViewHolder(view, adapter), + LayoutContainer { override val containerView: View? get() = contentView diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainModule.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainModule.kt index 1aebf27c..9d1c44c0 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainModule.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/main/MainModule.kt @@ -11,6 +11,7 @@ import io.github.wulkanowy.ui.modules.about.AboutFragment import io.github.wulkanowy.ui.modules.about.AboutModule import io.github.wulkanowy.ui.modules.account.AccountDialog import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment +import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment import io.github.wulkanowy.ui.modules.exam.ExamFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.grade.GradeModule @@ -41,6 +42,10 @@ abstract class MainModule { @ContributesAndroidInjector abstract fun bindAttendanceFragment(): AttendanceFragment + @PerFragment + @ContributesAndroidInjector + abstract fun bindAttendanceSummaryFragment(): AttendanceSummaryFragment + @PerFragment @ContributesAndroidInjector abstract fun bindExamFragment(): ExamFragment diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt index 60d3beaf..226fccd6 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/more/MoreFragment.kt @@ -72,7 +72,7 @@ class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.Mai get() { return context?.run { getString(R.string.about_title) to - ContextCompat.getDrawable(this, R.drawable.ic_more_about_24dp) + ContextCompat.getDrawable(this, R.drawable.ic_all_about_24dp) } } diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt index af9e26da..cfa110e9 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetableFragment.kt @@ -79,7 +79,7 @@ class TimetableFragment : BaseFragment(), TimetableView, MainView.MainChildView, } override fun resetView() { - timetableAdapter.smoothScrollToPosition(0) + timetableRecycler.smoothScrollToPosition(0) } override fun onFragmentReselected() { diff --git a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt index ef09d257..4c5630dd 100644 --- a/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt +++ b/app/src/main/java/io/github/wulkanowy/ui/modules/timetable/TimetablePresenter.kt @@ -58,7 +58,7 @@ class TimetablePresenter @Inject constructor( if (currentDate != it) { loadData(it) reloadView() - } else view?.resetView() + } else if (view?.isViewEmpty == false) view?.resetView() } } diff --git a/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt new file mode 100644 index 00000000..c2b1efaa --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/AttendanceExtension.kt @@ -0,0 +1,26 @@ +package io.github.wulkanowy.utils + +import io.github.wulkanowy.data.db.entities.AttendanceSummary + +/** + * [UONET+ - Zasady tworzenia podsumowań liczb uczniów obecnych i nieobecnych w tabeli frekwencji] + * (https://www.vulcan.edu.pl/vulcang_files/user/AABW/AABW-PDF/uonetplus/uonetplus_Frekwencja-liczby-obecnych-nieobecnych.pdf) + */ + +private inline val AttendanceSummary.allPresences: Double + get() = presence.toDouble() + absenceForSchoolReasons + lateness + latenessExcused + +private inline val AttendanceSummary.allAbsences: Double + get() = absence.toDouble() + absenceExcused + +fun AttendanceSummary.calculatePercentage() = calculatePercentage(allPresences, allAbsences) + +fun List.calculatePercentage(): Double { + return calculatePercentage(sumByDouble { it.allPresences }, sumByDouble { it.allAbsences }) +} + +private fun calculatePercentage(presence: Double, absence: Double): Double { + return if ((presence + absence) == 0.0) 0.0 else (presence / (presence + absence)) * 100 +} + + diff --git a/app/src/main/java/io/github/wulkanowy/utils/FabricUtils.kt b/app/src/main/java/io/github/wulkanowy/utils/FabricUtils.kt index a82c4f81..d8fc3a1d 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/FabricUtils.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/FabricUtils.kt @@ -5,6 +5,7 @@ import com.crashlytics.android.answers.CustomEvent import com.crashlytics.android.answers.LoginEvent import com.crashlytics.android.answers.SignUpEvent import timber.log.Timber +import kotlin.math.min fun logLogin(method: String) { try { @@ -20,7 +21,7 @@ fun logRegister(message: String, result: Boolean, symbol: String, endpoint: Stri .putMethod("Login activity") .putSuccess(result) .putCustomAttribute("symbol", symbol) - .putCustomAttribute("message", message) + .putCustomAttribute("message", message.substring(0, min(message.length, 100))) .putCustomAttribute("endpoint", endpoint) ) } catch (e: Throwable) { diff --git a/app/src/main/java/io/github/wulkanowy/utils/SpinnerExtension.kt b/app/src/main/java/io/github/wulkanowy/utils/SpinnerExtension.kt new file mode 100644 index 00000000..bea2bd3d --- /dev/null +++ b/app/src/main/java/io/github/wulkanowy/utils/SpinnerExtension.kt @@ -0,0 +1,14 @@ +package io.github.wulkanowy.utils + +import android.view.View +import android.widget.AdapterView +import android.widget.Spinner + +inline fun Spinner.setOnItemSelectedListener(crossinline listener: (view: View?) -> Unit) { + onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onNothingSelected(parent: AdapterView<*>?) {} + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + listener(view) + } + } +} 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 ffc4cc66..3cd38c73 100644 --- a/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt +++ b/app/src/main/java/io/github/wulkanowy/utils/TimeExtension.kt @@ -1,15 +1,22 @@ package io.github.wulkanowy.utils -import org.threeten.bp.DateTimeUtils -import org.threeten.bp.DayOfWeek.* +import org.threeten.bp.DayOfWeek.FRIDAY +import org.threeten.bp.DayOfWeek.MONDAY +import org.threeten.bp.DayOfWeek.SATURDAY +import org.threeten.bp.DayOfWeek.SUNDAY import org.threeten.bp.Instant import org.threeten.bp.LocalDate import org.threeten.bp.LocalDateTime +import org.threeten.bp.Month 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 java.util.* +import org.threeten.bp.format.TextStyle.FULL_STANDALONE +import org.threeten.bp.temporal.TemporalAdjusters.firstInMonth +import org.threeten.bp.temporal.TemporalAdjusters.next +import org.threeten.bp.temporal.TemporalAdjusters.previous +import java.util.Date +import java.util.Locale private const val DATE_PATTERN = "dd.MM.yyyy" @@ -29,6 +36,31 @@ fun LocalDate.toFormattedString(format: String = DATE_PATTERN): String = this.fo fun LocalDateTime.toFormattedString(format: String = DATE_PATTERN): String = this.format(ofPattern(format)) +/** + * https://github.com/ThreeTen/threetenbp/issues/55 + */ + +fun Month.getFormattedName(): String { + return getDisplayName(FULL_STANDALONE, Locale.getDefault()) + .let { + when (it) { + "stycznia" -> "Styczeń" + "lutego" -> "Luty" + "marca" -> "Marzec" + "kwietnia" -> "Kwiecień" + "maja" -> "Maj" + "czerwca" -> "Czerwiec" + "lipca" -> "Lipiec" + "sierpnia" -> "Sierpień" + "września" -> "Wrzesień" + "października" -> "Październik" + "listopada" -> "Listopad" + "grudnia" -> "Grudzień" + else -> it + } + } +} + inline val LocalDate.nextSchoolDay: LocalDate get() { return when (this.dayOfWeek) { @@ -73,6 +105,7 @@ inline val LocalDate.friday: LocalDate /** * [Dz.U. 2016 poz. 1335](http://prawo.sejm.gov.pl/isap.nsf/DocDetails.xsp?id=WDU20160001335) */ + inline val LocalDate.isHolidays: Boolean get() { return LocalDate.of(this.year, 9, 1).run { @@ -82,7 +115,7 @@ inline val LocalDate.isHolidays: Boolean } }.let { firstSchoolDay -> LocalDate.of(this.year, 6, 20) - .with(next(FRIDAY)) - .let { lastSchoolDay -> this.isBefore(firstSchoolDay) && this.isAfter(lastSchoolDay) } + .with(next(FRIDAY)) + .let { lastSchoolDay -> this.isBefore(firstSchoolDay) && this.isAfter(lastSchoolDay) } } } diff --git a/app/src/main/res/drawable/ic_more_about_24dp.xml b/app/src/main/res/drawable/ic_all_about_24dp.xml similarity index 83% rename from app/src/main/res/drawable/ic_more_about_24dp.xml rename to app/src/main/res/drawable/ic_all_about_24dp.xml index 6b81e32a..550f312e 100644 --- a/app/src/main/res/drawable/ic_more_about_24dp.xml +++ b/app/src/main/res/drawable/ic_all_about_24dp.xml @@ -4,9 +4,9 @@ android:viewportHeight="24" android:viewportWidth="24"> diff --git a/app/src/main/res/layout/fragment_attendance_summary.xml b/app/src/main/res/layout/fragment_attendance_summary.xml new file mode 100644 index 00000000..a3bdeb48 --- /dev/null +++ b/app/src/main/res/layout/fragment_attendance_summary.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_attendance_summary.xml b/app/src/main/res/layout/item_attendance_summary.xml new file mode 100644 index 00000000..ac21f68d --- /dev/null +++ b/app/src/main/res/layout/item_attendance_summary.xml @@ -0,0 +1,252 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_attendance_summary_subject.xml b/app/src/main/res/layout/item_attendance_summary_subject.xml new file mode 100644 index 00000000..ce8e9b5e --- /dev/null +++ b/app/src/main/res/layout/item_attendance_summary_subject.xml @@ -0,0 +1,14 @@ + + diff --git a/app/src/main/res/layout/scrollable_header_attendance_summary.xml b/app/src/main/res/layout/scrollable_header_attendance_summary.xml new file mode 100644 index 00000000..37bbb32b --- /dev/null +++ b/app/src/main/res/layout/scrollable_header_attendance_summary.xml @@ -0,0 +1,25 @@ + + + + + + + diff --git a/app/src/main/res/menu/action_menu_attendance.xml b/app/src/main/res/menu/action_menu_attendance.xml new file mode 100644 index 00000000..efafe135 --- /dev/null +++ b/app/src/main/res/menu/action_menu_attendance.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/menu/action_menu_grade.xml b/app/src/main/res/menu/action_menu_grade.xml index 87d6ad80..fdc1dbd0 100644 --- a/app/src/main/res/menu/action_menu_grade.xml +++ b/app/src/main/res/menu/action_menu_grade.xml @@ -1,6 +1,6 @@ + - %1$d nieobecności + + Frekwencja Brak sprawdzianów w tym tygodniu diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fb955f3b..0bc0699c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -108,6 +108,9 @@ %1$d absences + + Attendance + No exams in this week diff --git a/app/src/test/java/io/github/wulkanowy/utils/TimeExtensionTest.kt b/app/src/test/java/io/github/wulkanowy/utils/TimeExtensionTest.kt index ae39283e..442356dc 100644 --- a/app/src/test/java/io/github/wulkanowy/utils/TimeExtensionTest.kt +++ b/app/src/test/java/io/github/wulkanowy/utils/TimeExtensionTest.kt @@ -1,10 +1,13 @@ package io.github.wulkanowy.utils -import org.junit.Assert.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Test import org.threeten.bp.LocalDate import org.threeten.bp.LocalDateTime -import java.util.* +import org.threeten.bp.Month.JANUARY +import java.util.Locale class TimeExtensionTest { @@ -43,6 +46,14 @@ class TimeExtensionTest { assertEquals(LocalDate.of(2018, 10, 12), LocalDate.of(2018, 10, 8).friday) } + @Test + fun monthNameTest() { + Locale.setDefault(Locale.forLanguageTag("PL")) + assertEquals("Styczeń", JANUARY.getFormattedName()) + Locale.setDefault(Locale.forLanguageTag("US")) + assertEquals("January", JANUARY.getFormattedName()) + } + @Test fun weekDayNameTest() { Locale.setDefault(Locale.forLanguageTag("PL")) @@ -74,7 +85,6 @@ class TimeExtensionTest { assertEquals(LocalDate.of(2018, 10, 3), LocalDate.of(2018, 10, 4).previousSchoolDay) } - @Test fun nextOrSameSchoolDayTest() { assertEquals(LocalDate.of(2018, 9, 28), LocalDate.of(2018, 9, 28).nextOrSameSchoolDay)