forked from github/wulkanowy-mirror
Add a summary of attendance (#132)
This commit is contained in:
parent
900065d758
commit
f96d0ebed9
@ -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"
|
||||
|
@ -1,3 +1,3 @@
|
||||
-keep class android.support.test.internal** { *; }
|
||||
-keep class org.junit.** { *; }
|
||||
-keep public class io.github.wulkanowy** { *; }
|
||||
-keep public class io.github.wulkanowy** { *; }
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) }
|
||||
}
|
||||
|
@ -20,5 +20,5 @@ interface AttendanceDao {
|
||||
fun deleteAll(exams: List<Attendance>)
|
||||
|
||||
@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<List<Attendance>>
|
||||
fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe<List<Attendance>>
|
||||
}
|
||||
|
@ -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<AttendanceSummary>): List<Long>
|
||||
|
||||
@Delete
|
||||
fun deleteAll(exams: List<AttendanceSummary>)
|
||||
|
||||
@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<List<AttendanceSummary>>
|
||||
}
|
@ -20,5 +20,5 @@ interface ExamDao {
|
||||
fun deleteAll(exams: List<Exam>)
|
||||
|
||||
@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<List<Exam>>
|
||||
fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe<List<Exam>>
|
||||
}
|
||||
|
@ -26,8 +26,8 @@ interface GradeDao {
|
||||
fun deleteAll(grades: List<Grade>)
|
||||
|
||||
@Query("SELECT * FROM Grades WHERE semester_id = :semesterId AND student_id = :studentId")
|
||||
fun load(semesterId: Int, studentId: Int): Maybe<List<Grade>>
|
||||
fun loadAll(semesterId: Int, studentId: Int): Maybe<List<Grade>>
|
||||
|
||||
@Query("SELECT * FROM Grades WHERE is_read = 0 AND semester_id = :semesterId AND student_id = :studentId")
|
||||
fun loadNew(semesterId: Int, studentId: Int): Maybe<List<Grade>>
|
||||
fun loadAllNew(semesterId: Int, studentId: Int): Maybe<List<Grade>>
|
||||
}
|
||||
|
@ -19,5 +19,5 @@ interface GradeSummaryDao {
|
||||
fun deleteAll(gradesSummary: List<GradeSummary>)
|
||||
|
||||
@Query("SELECT * FROM grades_summary WHERE student_id = :studentId AND semester_id = :semesterId")
|
||||
fun load(semesterId: Int, studentId: Int): Maybe<List<GradeSummary>>
|
||||
fun loadAll(semesterId: Int, studentId: Int): Maybe<List<GradeSummary>>
|
||||
}
|
||||
|
@ -20,5 +20,5 @@ interface HomeworkDao {
|
||||
fun deleteAll(homework: List<Homework>)
|
||||
|
||||
@Query("SELECT * FROM Homework WHERE semester_id = :semesterId AND student_id = :studentId AND date = :date")
|
||||
fun load(semesterId: Int, studentId: Int, date: LocalDate): Maybe<List<Homework>>
|
||||
fun loadAll(semesterId: Int, studentId: Int, date: LocalDate): Maybe<List<Homework>>
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ interface NoteDao {
|
||||
fun deleteAll(notes: List<Note>)
|
||||
|
||||
@Query("SELECT * FROM Notes WHERE semester_id = :semesterId AND student_id = :studentId")
|
||||
fun load(semesterId: Int, studentId: Int): Maybe<List<Note>>
|
||||
fun loadAll(semesterId: Int, studentId: Int): Maybe<List<Note>>
|
||||
|
||||
@Query("SELECT * FROM Notes WHERE is_read = 0 AND semester_id = :semesterId AND student_id = :studentId")
|
||||
fun loadNew(semesterId: Int, studentId: Int): Maybe<List<Note>>
|
||||
|
@ -16,7 +16,7 @@ interface SemesterDao {
|
||||
fun insertAll(semester: List<Semester>)
|
||||
|
||||
@Query("SELECT * FROM Semesters WHERE student_id = :studentId")
|
||||
fun load(studentId: Int): Maybe<List<Semester>>
|
||||
fun loadAll(studentId: Int): Maybe<List<Semester>>
|
||||
|
||||
@Query("UPDATE Semesters SET is_current = 1 WHERE semester_id = :semesterId AND diary_id = :diaryId")
|
||||
fun updateCurrent(semesterId: Int, diaryId: Int)
|
||||
|
@ -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<Subject>): List<Long>
|
||||
|
||||
@Delete
|
||||
fun deleteAll(subjects: List<Subject>)
|
||||
|
||||
@Query("SELECT * FROM Subjects WHERE diary_id = :diaryId AND student_id = :studentId")
|
||||
fun loadAll(diaryId: Int, studentId: Int): Maybe<List<Subject>>
|
||||
}
|
@ -20,5 +20,5 @@ interface TimetableDao {
|
||||
fun deleteAll(exams: List<Timetable>)
|
||||
|
||||
@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<List<Timetable>>
|
||||
fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe<List<Timetable>>
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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<List<Attendance>> {
|
||||
: Single<List<Attendance>> {
|
||||
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 } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<List<AttendanceSummary>>? {
|
||||
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()) })
|
||||
}
|
||||
}
|
@ -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<List<GradeSummary>> {
|
||||
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()) })
|
||||
}
|
||||
}
|
||||
|
@ -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<List<Subject>> {
|
||||
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())
|
||||
})
|
||||
}
|
||||
}
|
@ -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<List<Timetable>> {
|
||||
: Single<List<Timetable>> {
|
||||
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 } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<List<Attendance>> {
|
||||
return attendanceDb.load(semester.diaryId, semester.studentId, startDate, endDate)
|
||||
return attendanceDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate)
|
||||
.filter { !it.isEmpty() }
|
||||
}
|
||||
|
||||
|
@ -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<List<AttendanceSummary>> {
|
||||
return attendanceDb.loadAll(semester.diaryId, semester.studentId, subjectId).filter { !it.isEmpty() }
|
||||
}
|
||||
|
||||
fun saveAttendanceSummary(attendance: List<AttendanceSummary>) {
|
||||
attendanceDb.insertAll(attendance)
|
||||
}
|
||||
|
||||
fun deleteAttendanceSummary(attendance: List<AttendanceSummary>) {
|
||||
attendanceDb.deleteAll(attendance)
|
||||
}
|
||||
}
|
@ -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<List<Exam>> {
|
||||
return examDb.load(semester.diaryId, semester.studentId, startDate, endDate)
|
||||
return examDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate)
|
||||
.filter { !it.isEmpty() }
|
||||
}
|
||||
|
||||
|
@ -12,11 +12,11 @@ import javax.inject.Singleton
|
||||
class GradeLocal @Inject constructor(private val gradeDb: GradeDao) {
|
||||
|
||||
fun getGrades(semester: Semester): Maybe<List<Grade>> {
|
||||
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<List<Grade>> {
|
||||
return gradeDb.loadNew(semester.semesterId, semester.studentId)
|
||||
return gradeDb.loadAllNew(semester.semesterId, semester.studentId)
|
||||
}
|
||||
|
||||
fun saveGrades(grades: List<Grade>) {
|
||||
|
@ -11,7 +11,7 @@ import javax.inject.Singleton
|
||||
class GradeSummaryLocal @Inject constructor(private val gradeSummaryDb: GradeSummaryDao) {
|
||||
|
||||
fun getGradesSummary(semester: Semester): Maybe<List<GradeSummary>> {
|
||||
return gradeSummaryDb.load(semester.semesterId, semester.studentId)
|
||||
return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId)
|
||||
.filter { !it.isEmpty() }
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ import javax.inject.Singleton
|
||||
class HomeworkLocal @Inject constructor(private val homeworkDb: HomeworkDao) {
|
||||
|
||||
fun getHomework(semester: Semester, date: LocalDate): Maybe<List<Homework>> {
|
||||
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<Homework>) {
|
||||
|
@ -12,7 +12,7 @@ import javax.inject.Singleton
|
||||
class NoteLocal @Inject constructor(private val noteDb: NoteDao) {
|
||||
|
||||
fun getNotes(semester: Semester): Maybe<List<Note>> {
|
||||
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<List<Note>> {
|
||||
|
@ -15,7 +15,7 @@ class SemesterLocal @Inject constructor(private val semesterDb: SemesterDao) {
|
||||
}
|
||||
|
||||
fun getSemesters(student: Student): Maybe<List<Semester>> {
|
||||
return semesterDb.load(student.studentId).filter { !it.isEmpty() }
|
||||
return semesterDb.loadAll(student.studentId).filter { !it.isEmpty() }
|
||||
}
|
||||
|
||||
fun setCurrentSemester(semester: Semester) {
|
||||
|
@ -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<List<Subject>> {
|
||||
return subjectDao.loadAll(semester.diaryId, semester.studentId)
|
||||
.filter { !it.isEmpty() }
|
||||
}
|
||||
|
||||
fun saveSubjects(subjects: List<Subject>) {
|
||||
subjectDao.insertAll(subjects)
|
||||
}
|
||||
|
||||
fun deleteSubjects(subjects: List<Subject>) {
|
||||
subjectDao.deleteAll(subjects)
|
||||
}
|
||||
}
|
@ -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<List<Timetable>> {
|
||||
return timetableDb.load(semester.diaryId, semester.studentId, startDate, endDate)
|
||||
return timetableDb.loadAll(semester.diaryId, semester.studentId, startDate, endDate)
|
||||
.filter { !it.isEmpty() }
|
||||
}
|
||||
|
||||
|
@ -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<List<Attendance>> {
|
||||
|
@ -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<List<AttendanceSummary>> {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<List<Subject>> {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<AttendanceItem>) {
|
||||
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())
|
||||
|
@ -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 {
|
||||
|
@ -7,6 +7,8 @@ interface AttendanceView : BaseView {
|
||||
|
||||
val isViewEmpty: Boolean
|
||||
|
||||
val currentStackSize: Int?
|
||||
|
||||
fun initView()
|
||||
|
||||
fun updateData(data: List<AttendanceItem>)
|
||||
@ -30,4 +32,8 @@ interface AttendanceView : BaseView {
|
||||
fun showNextButton(show: Boolean)
|
||||
|
||||
fun showAttendanceDialog(lesson: Attendance)
|
||||
|
||||
fun openSummaryView()
|
||||
|
||||
fun popView()
|
||||
}
|
||||
|
@ -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<AbstractFlexibleItem<*>>
|
||||
|
||||
private lateinit var subjectsAdapter: ArrayAdapter<String>
|
||||
|
||||
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<String>())
|
||||
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<String>) {
|
||||
subjectsAdapter.run {
|
||||
clear()
|
||||
addAll(data)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateDataSet(data: List<AttendanceSummaryItem>, 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()
|
||||
}
|
||||
}
|
@ -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<AttendanceSummaryItem.ViewHolder>() {
|
||||
|
||||
override fun getLayoutRes() = R.layout.item_attendance_summary
|
||||
|
||||
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?): ViewHolder {
|
||||
return ViewHolder(view, adapter)
|
||||
}
|
||||
|
||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder?, position: Int, payloads: MutableList<Any>?) {
|
||||
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<IFlexible<*>>?) : FlexibleViewHolder(view, adapter),
|
||||
LayoutContainer {
|
||||
|
||||
override val containerView: View?
|
||||
get() = contentView
|
||||
}
|
||||
}
|
@ -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<AttendanceSummaryView>(errorHandler) {
|
||||
|
||||
private var subjects = emptyList<Subject>()
|
||||
|
||||
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<AttendanceSummary>): List<AttendanceSummaryItem> {
|
||||
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)}%"
|
||||
}
|
||||
}
|
@ -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<AttendanceSummaryScrollableHeader.ViewHolder>() {
|
||||
|
||||
override fun getLayoutRes() = R.layout.scrollable_header_attendance_summary
|
||||
|
||||
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?): ViewHolder {
|
||||
return ViewHolder(view, adapter)
|
||||
}
|
||||
|
||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder?, position: Int, payloads: MutableList<Any>?) {
|
||||
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<IFlexible<*>>?) : FlexibleViewHolder(view, adapter),
|
||||
LayoutContainer {
|
||||
|
||||
override val containerView: View?
|
||||
get() = contentView
|
||||
}
|
||||
}
|
@ -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<AttendanceSummaryItem>, header: AttendanceSummaryScrollableHeader)
|
||||
|
||||
fun updateSubjects(data: ArrayList<String>)
|
||||
|
||||
fun showSubjects(show: Boolean)
|
||||
|
||||
fun clearView()
|
||||
}
|
@ -78,7 +78,7 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView.
|
||||
}
|
||||
|
||||
override fun resetView() {
|
||||
examAdapter.smoothScrollToPosition(0)
|
||||
examRecycler.scrollToPosition(0)
|
||||
}
|
||||
|
||||
override fun onFragmentReselected() {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<GradeSummaryItem.ViewHolder, GradeSummaryHeader>(header) {
|
||||
class GradeSummaryItem(header: GradeSummaryHeader, private val grade: String, private val title: String) :
|
||||
AbstractSectionableItem<GradeSummaryItem.ViewHolder, GradeSummaryHeader>(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<IFlexible<*>>?, holder: ViewHolder?,
|
||||
position: Int, payloads: MutableList<Any>?) {
|
||||
override fun bindViewHolder(
|
||||
adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder?,
|
||||
position: Int, payloads: MutableList<Any>?
|
||||
) {
|
||||
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<IFlexible<*>>?)
|
||||
: FlexibleViewHolder(view, adapter), LayoutContainer {
|
||||
class ViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?) : FlexibleViewHolder(view, adapter),
|
||||
LayoutContainer {
|
||||
|
||||
override val containerView: View?
|
||||
get() = contentView
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ class TimetableFragment : BaseFragment(), TimetableView, MainView.MainChildView,
|
||||
}
|
||||
|
||||
override fun resetView() {
|
||||
timetableAdapter.smoothScrollToPosition(0)
|
||||
timetableRecycler.smoothScrollToPosition(0)
|
||||
}
|
||||
|
||||
override fun onFragmentReselected() {
|
||||
|
@ -58,7 +58,7 @@ class TimetablePresenter @Inject constructor(
|
||||
if (currentDate != it) {
|
||||
loadData(it)
|
||||
reloadView()
|
||||
} else view?.resetView()
|
||||
} else if (view?.isViewEmpty == false) view?.resetView()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<AttendanceSummary>.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
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,9 @@
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M11,7h2v2h-2zM11,11h2v6h-2z" />
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M12,2a10,10 0,1 0,0 20,10 10,0 0,0 0,-20zM12,20a8,8 0,1 1,0 -16,8 8,0 0,1 0,16z" />
|
||||
</vector>
|
66
app/src/main/res/layout/fragment_attendance_summary.xml
Normal file
66
app/src/main/res/layout/fragment_attendance_summary.xml
Normal file
@ -0,0 +1,66 @@
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatSpinner
|
||||
android:id="@+id/attendanceSummarySubjects"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="invisible"
|
||||
android:background="?android:attr/windowBackground"
|
||||
android:elevation="5dp"
|
||||
android:padding="15dp"
|
||||
android:spinnerMode="dialog" />
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/attendanceSummarySwipe"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/attendanceSummaryRecycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/attendanceSummaryProgress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/attendanceSummaryEmpty"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="invisible">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="100dp"
|
||||
android:minHeight="100dp"
|
||||
app:srcCompat="@drawable/ic_menu_main_attendance_24dp"
|
||||
app:tint="?android:attr/textColorPrimary"
|
||||
tools:ignore="contentDescription" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/attendance_no_items"
|
||||
android:textSize="20sp" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
252
app/src/main/res/layout/item_attendance_summary.xml
Normal file
252
app/src/main/res/layout/item_attendance_summary.xml
Normal file
@ -0,0 +1,252 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:foreground="?attr/colorControlHighlight"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="20dp"
|
||||
android:paddingTop="7dp"
|
||||
android:paddingRight="20dp"
|
||||
android:paddingBottom="7dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/attendanceSummaryMonth"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="40dp"
|
||||
android:layout_marginRight="40dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="17sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/attendanceSummaryPercentage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/ic_all_divider"
|
||||
android:minHeight="35dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginLeft="20dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/attendance_present"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/attendanceSummaryPresent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginEnd="25dp"
|
||||
android:layout_marginRight="25dp"
|
||||
android:gravity="end"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/ic_all_divider"
|
||||
android:minHeight="35dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginLeft="20dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/attendance_absence_unexcused"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/attendanceSummaryAbsenceUnexcused"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginEnd="25dp"
|
||||
android:layout_marginRight="25dp"
|
||||
android:gravity="end"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/ic_all_divider"
|
||||
android:minHeight="35dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginLeft="20dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/attendance_absence_excused"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/attendanceSummaryAbsenceExcused"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginEnd="25dp"
|
||||
android:layout_marginRight="25dp"
|
||||
android:gravity="end"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/ic_all_divider"
|
||||
android:minHeight="35dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginLeft="20dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/attendance_absence_school"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/attendanceSummaryAbsenceSchool"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginEnd="25dp"
|
||||
android:layout_marginRight="25dp"
|
||||
android:gravity="end"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/ic_all_divider"
|
||||
android:minHeight="35dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginLeft="20dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/attendance_exemption"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/attendanceSummaryExemption"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginEnd="25dp"
|
||||
android:layout_marginRight="25dp"
|
||||
android:gravity="end"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/ic_all_divider"
|
||||
android:minHeight="35dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginLeft="20dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/attendance_unexcused_lateness"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/attendanceSummaryLatenessUnexcused"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginEnd="25dp"
|
||||
android:layout_marginRight="25dp"
|
||||
android:gravity="end"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/ic_all_divider"
|
||||
android:minHeight="35dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginLeft="20dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/attendance_excused_lateness"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/attendanceSummaryLatenessExcused"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginEnd="25dp"
|
||||
android:layout_marginRight="25dp"
|
||||
android:gravity="end"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
14
app/src/main/res/layout/item_attendance_summary_subject.xml
Normal file
14
app/src/main/res/layout/item_attendance_summary_subject.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/attendanceSummaryItemSubject"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:gravity="start"
|
||||
android:maxLines="1"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingRight="16dp"
|
||||
android:textAlignment="textStart"
|
||||
android:paddingBottom="12dp"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="16sp" />
|
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:minLines="2"
|
||||
android:text="@string/attendance_title"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/attendanceSummaryScrollableHeaderPercentage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="100%"
|
||||
android:textSize="21sp" />
|
||||
</LinearLayout>
|
10
app/src/main/res/menu/action_menu_attendance.xml
Normal file
10
app/src/main/res/menu/action_menu_attendance.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/attendanceMenuSummary"
|
||||
android:icon="@drawable/ic_all_about_24dp"
|
||||
android:orderInCategory="1"
|
||||
android:title="@string/grade_switch_semester"
|
||||
app:showAsAction="ifRoom" />
|
||||
</menu>
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/gradeMenuSemester"
|
||||
android:icon="@drawable/ic_menu_grade_semester_24dp"
|
||||
|
@ -118,6 +118,8 @@
|
||||
<item quantity="many">%1$d nieobecności</item>
|
||||
</plurals>
|
||||
|
||||
<!--Attendance summary-->
|
||||
<string name="attendance_summary_final">Frekwencja</string>
|
||||
|
||||
<!--Exam-->
|
||||
<string name="exam_no_items">Brak sprawdzianów w tym tygodniu</string>
|
||||
|
@ -108,6 +108,9 @@
|
||||
<item quantity="other">%1$d absences</item>
|
||||
</plurals>
|
||||
|
||||
<!--Attendance summary-->
|
||||
<string name="attendance_summary_final">Attendance</string>
|
||||
|
||||
|
||||
<!--Exam-->
|
||||
<string name="exam_no_items">No exams in this week</string>
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user