Add a summary of attendance (#132)

This commit is contained in:
Rafał Borcz 2018-12-07 19:01:19 +01:00 committed by Mikołaj Pich
parent 900065d758
commit f96d0ebed9
65 changed files with 1351 additions and 126 deletions

View File

@ -81,7 +81,7 @@ configurations.all {
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$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.legacy:legacy-support-v4:1.0.0"
implementation "androidx.appcompat:appcompat:1.0.2" implementation "androidx.appcompat:appcompat:1.0.2"

View File

@ -77,6 +77,10 @@ internal class RepositoryModule {
@Provides @Provides
fun provideAttendanceDao(database: AppDatabase) = database.attendanceDao fun provideAttendanceDao(database: AppDatabase) = database.attendanceDao
@Singleton
@Provides
fun provideAttendanceSummaryDao(database: AppDatabase) = database.attendanceSummaryDao
@Singleton @Singleton
@Provides @Provides
fun provideTimetableDao(database: AppDatabase) = database.timetableDao fun provideTimetableDao(database: AppDatabase) = database.timetableDao
@ -88,4 +92,8 @@ internal class RepositoryModule {
@Singleton @Singleton
@Provides @Provides
fun provideHomeworkDao(database: AppDatabase) = database.homeworkDao fun provideHomeworkDao(database: AppDatabase) = database.homeworkDao
@Singleton
@Provides
fun provideSubjectDao(database: AppDatabase) = database.subjectDao
} }

View File

@ -7,6 +7,7 @@ import androidx.room.RoomDatabase
import androidx.room.RoomDatabase.JournalMode.TRUNCATE import androidx.room.RoomDatabase.JournalMode.TRUNCATE
import androidx.room.TypeConverters import androidx.room.TypeConverters
import io.github.wulkanowy.data.db.dao.AttendanceDao 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.ExamDao
import io.github.wulkanowy.data.db.dao.GradeDao import io.github.wulkanowy.data.db.dao.GradeDao
import io.github.wulkanowy.data.db.dao.GradeSummaryDao 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.NoteDao
import io.github.wulkanowy.data.db.dao.SemesterDao import io.github.wulkanowy.data.db.dao.SemesterDao
import io.github.wulkanowy.data.db.dao.StudentDao 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.dao.TimetableDao
import io.github.wulkanowy.data.db.entities.Attendance 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.Exam
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.GradeSummary 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.Note
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student 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 io.github.wulkanowy.data.db.entities.Timetable
import javax.inject.Singleton import javax.inject.Singleton
@ -36,11 +40,13 @@ import javax.inject.Singleton
Exam::class, Exam::class,
Timetable::class, Timetable::class,
Attendance::class, Attendance::class,
AttendanceSummary::class,
Grade::class, Grade::class,
GradeSummary::class, GradeSummary::class,
Message::class, Message::class,
Note::class, Note::class,
Homework::class Homework::class,
Subject::class
], ],
version = 1, version = 1,
exportSchema = false exportSchema = false
@ -66,6 +72,8 @@ abstract class AppDatabase : RoomDatabase() {
abstract val attendanceDao: AttendanceDao abstract val attendanceDao: AttendanceDao
abstract val attendanceSummaryDao: AttendanceSummaryDao
abstract val gradeDao: GradeDao abstract val gradeDao: GradeDao
abstract val gradeSummaryDao: GradeSummaryDao abstract val gradeSummaryDao: GradeSummaryDao
@ -75,4 +83,6 @@ abstract class AppDatabase : RoomDatabase() {
abstract val noteDao: NoteDao abstract val noteDao: NoteDao
abstract val homeworkDao: HomeworkDao abstract val homeworkDao: HomeworkDao
abstract val subjectDao: SubjectDao
} }

View File

@ -1,8 +1,13 @@
package io.github.wulkanowy.data.db package io.github.wulkanowy.data.db
import androidx.room.TypeConverter import androidx.room.TypeConverter
import org.threeten.bp.* import org.threeten.bp.DateTimeUtils
import java.util.* 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 { class Converters {
@ -25,4 +30,10 @@ class Converters {
fun timeToTimestamp(date: LocalDateTime?): Long? { fun timeToTimestamp(date: LocalDateTime?): Long? {
return date?.atZone(ZoneOffset.UTC)?.toInstant()?.toEpochMilli() return date?.atZone(ZoneOffset.UTC)?.toInstant()?.toEpochMilli()
} }
@TypeConverter
fun monthToInt(month: Month?) = month?.value
@TypeConverter
fun intToMonth(value: Int?) = value?.let { Month.of(it) }
} }

View File

@ -20,5 +20,5 @@ interface AttendanceDao {
fun deleteAll(exams: List<Attendance>) fun deleteAll(exams: List<Attendance>)
@Query("SELECT * FROM Attendance WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") @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>>
} }

View File

@ -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>>
}

View File

@ -20,5 +20,5 @@ interface ExamDao {
fun deleteAll(exams: List<Exam>) fun deleteAll(exams: List<Exam>)
@Query("SELECT * FROM Exams WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") @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>>
} }

View File

@ -26,8 +26,8 @@ interface GradeDao {
fun deleteAll(grades: List<Grade>) fun deleteAll(grades: List<Grade>)
@Query("SELECT * FROM Grades WHERE semester_id = :semesterId AND student_id = :studentId") @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") @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>>
} }

View File

@ -19,5 +19,5 @@ interface GradeSummaryDao {
fun deleteAll(gradesSummary: List<GradeSummary>) fun deleteAll(gradesSummary: List<GradeSummary>)
@Query("SELECT * FROM grades_summary WHERE student_id = :studentId AND semester_id = :semesterId") @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>>
} }

View File

@ -20,5 +20,5 @@ interface HomeworkDao {
fun deleteAll(homework: List<Homework>) fun deleteAll(homework: List<Homework>)
@Query("SELECT * FROM Homework WHERE semester_id = :semesterId AND student_id = :studentId AND date = :date") @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>>
} }

View File

@ -26,7 +26,7 @@ interface NoteDao {
fun deleteAll(notes: List<Note>) fun deleteAll(notes: List<Note>)
@Query("SELECT * FROM Notes WHERE semester_id = :semesterId AND student_id = :studentId") @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") @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>> fun loadNew(semesterId: Int, studentId: Int): Maybe<List<Note>>

View File

@ -16,7 +16,7 @@ interface SemesterDao {
fun insertAll(semester: List<Semester>) fun insertAll(semester: List<Semester>)
@Query("SELECT * FROM Semesters WHERE student_id = :studentId") @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") @Query("UPDATE Semesters SET is_current = 1 WHERE semester_id = :semesterId AND diary_id = :diaryId")
fun updateCurrent(semesterId: Int, diaryId: Int) fun updateCurrent(semesterId: Int, diaryId: Int)

View File

@ -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>>
}

View File

@ -20,5 +20,5 @@ interface TimetableDao {
fun deleteAll(exams: List<Timetable>) fun deleteAll(exams: List<Timetable>)
@Query("SELECT * FROM Timetable WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end") @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>>
} }

View File

@ -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
}

View File

@ -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
}

View File

@ -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()) })
}
}

View File

@ -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())
})
}
}

View File

@ -10,7 +10,7 @@ import javax.inject.Inject
class AttendanceLocal @Inject constructor(private val attendanceDb: AttendanceDao) { class AttendanceLocal @Inject constructor(private val attendanceDb: AttendanceDao) {
fun getAttendance(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe<List<Attendance>> { 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() } .filter { !it.isEmpty() }
} }

View File

@ -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)
}
}

View File

@ -10,7 +10,7 @@ import javax.inject.Inject
class ExamLocal @Inject constructor(private val examDb: ExamDao) { class ExamLocal @Inject constructor(private val examDb: ExamDao) {
fun getExams(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe<List<Exam>> { 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() } .filter { !it.isEmpty() }
} }

View File

@ -12,11 +12,11 @@ import javax.inject.Singleton
class GradeLocal @Inject constructor(private val gradeDb: GradeDao) { class GradeLocal @Inject constructor(private val gradeDb: GradeDao) {
fun getGrades(semester: Semester): Maybe<List<Grade>> { 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>> { 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>) { fun saveGrades(grades: List<Grade>) {

View File

@ -11,7 +11,7 @@ import javax.inject.Singleton
class GradeSummaryLocal @Inject constructor(private val gradeSummaryDb: GradeSummaryDao) { class GradeSummaryLocal @Inject constructor(private val gradeSummaryDb: GradeSummaryDao) {
fun getGradesSummary(semester: Semester): Maybe<List<GradeSummary>> { fun getGradesSummary(semester: Semester): Maybe<List<GradeSummary>> {
return gradeSummaryDb.load(semester.semesterId, semester.studentId) return gradeSummaryDb.loadAll(semester.semesterId, semester.studentId)
.filter { !it.isEmpty() } .filter { !it.isEmpty() }
} }

View File

@ -12,7 +12,7 @@ import javax.inject.Singleton
class HomeworkLocal @Inject constructor(private val homeworkDb: HomeworkDao) { class HomeworkLocal @Inject constructor(private val homeworkDb: HomeworkDao) {
fun getHomework(semester: Semester, date: LocalDate): Maybe<List<Homework>> { 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>) { fun saveHomework(homework: List<Homework>) {

View File

@ -12,7 +12,7 @@ import javax.inject.Singleton
class NoteLocal @Inject constructor(private val noteDb: NoteDao) { class NoteLocal @Inject constructor(private val noteDb: NoteDao) {
fun getNotes(semester: Semester): Maybe<List<Note>> { 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>> { fun getNewNotes(semester: Semester): Maybe<List<Note>> {

View File

@ -15,7 +15,7 @@ class SemesterLocal @Inject constructor(private val semesterDb: SemesterDao) {
} }
fun getSemesters(student: Student): Maybe<List<Semester>> { 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) { fun setCurrentSemester(semester: Semester) {

View File

@ -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)
}
}

View File

@ -10,7 +10,7 @@ import javax.inject.Inject
class TimetableLocal @Inject constructor(private val timetableDb: TimetableDao) { class TimetableLocal @Inject constructor(private val timetableDb: TimetableDao) {
fun getTimetable(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe<List<Timetable>> { 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() } .filter { !it.isEmpty() }
} }

View File

@ -7,7 +7,9 @@ import io.github.wulkanowy.utils.toLocalDate
import io.reactivex.Single import io.reactivex.Single
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AttendanceRemote @Inject constructor(private val api: Api) { class AttendanceRemote @Inject constructor(private val api: Api) {
fun getAttendance(semester: Semester, startDate: LocalDate, endDate: LocalDate): Single<List<Attendance>> { fun getAttendance(semester: Semester, startDate: LocalDate, endDate: LocalDate): Single<List<Attendance>> {

View File

@ -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
)
}
}
}
}

View File

@ -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
)
}
}
}
}

View File

@ -2,6 +2,9 @@ package io.github.wulkanowy.ui.modules.attendance
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
@ -10,6 +13,8 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.ui.base.BaseFragment 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.ui.modules.main.MainView
import io.github.wulkanowy.utils.setOnItemClickListener import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_attendance.* import kotlinx.android.synthetic.main.fragment_attendance.*
@ -35,6 +40,14 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
override val isViewEmpty: Boolean override val isViewEmpty: Boolean
get() = attendanceAdapter.isEmpty 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? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_attendance, container, false) return inflater.inflate(R.layout.fragment_attendance, container, false)
} }
@ -59,6 +72,15 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
attendanceNextButton.setOnClickListener { presenter.onNextDay() } 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>) { override fun updateData(data: List<AttendanceItem>) {
attendanceAdapter.updateDataSet(data, true) attendanceAdapter.updateDataSet(data, true)
} }
@ -72,13 +94,17 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
} }
override fun resetView() { override fun resetView() {
attendanceAdapter.smoothScrollToPosition(0) attendanceRecycler.smoothScrollToPosition(0)
} }
override fun onFragmentReselected() { override fun onFragmentReselected() {
presenter.onViewReselected() presenter.onViewReselected()
} }
override fun popView() {
(activity as? MainActivity)?.popView()
}
override fun showEmpty(show: Boolean) { override fun showEmpty(show: Boolean) {
attendanceEmpty.visibility = if (show) View.VISIBLE else View.GONE 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()) AttendanceDialog.newInstance(lesson).show(fragmentManager, lesson.toString())
} }
override fun openSummaryView() {
(activity as? MainActivity)?.pushView(AttendanceSummaryFragment.newInstance())
}
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay()) outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay())

View File

@ -56,11 +56,15 @@ class AttendancePresenter @Inject constructor(
} }
fun onViewReselected() { fun onViewReselected() {
view?.also { view ->
if (view.currentStackSize == 1) {
now().previousOrSameSchoolDay.also { now().previousOrSameSchoolDay.also {
if (currentDate != it) { if (currentDate != it) {
loadData(it) loadData(it)
reloadView() reloadView()
} else view?.resetView() } 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) if (item is AttendanceItem) view?.showAttendanceDialog(item.attendance)
} }
fun onSummarySwitchSelected(): Boolean {
view?.openSummaryView()
return true
}
private fun loadData(date: LocalDate, forceRefresh: Boolean = false) { private fun loadData(date: LocalDate, forceRefresh: Boolean = false) {
currentDate = date currentDate = date
disposable.apply { disposable.apply {

View File

@ -7,6 +7,8 @@ interface AttendanceView : BaseView {
val isViewEmpty: Boolean val isViewEmpty: Boolean
val currentStackSize: Int?
fun initView() fun initView()
fun updateData(data: List<AttendanceItem>) fun updateData(data: List<AttendanceItem>)
@ -30,4 +32,8 @@ interface AttendanceView : BaseView {
fun showNextButton(show: Boolean) fun showNextButton(show: Boolean)
fun showAttendanceDialog(lesson: Attendance) fun showAttendanceDialog(lesson: Attendance)
fun openSummaryView()
fun popView()
} }

View File

@ -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()
}
}

View File

@ -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
}
}

View File

@ -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)}%"
}
}

View File

@ -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
}
}

View File

@ -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()
}

View File

@ -78,7 +78,7 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView.
} }
override fun resetView() { override fun resetView() {
examAdapter.smoothScrollToPosition(0) examRecycler.scrollToPosition(0)
} }
override fun onFragmentReselected() { override fun onFragmentReselected() {

View File

@ -63,8 +63,7 @@ class ExamPresenter @Inject constructor(
if (currentDate != it) { if (currentDate != it) {
loadData(it) loadData(it)
reloadView() reloadView()
view?.resetView() } else if (view?.isViewEmpty == false) view?.resetView()
} else view?.resetView()
} }
} }

View File

@ -6,11 +6,12 @@ import eu.davidea.flexibleadapter.items.AbstractSectionableItem
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.GradeSummary
import kotlinx.android.extensions.LayoutContainer import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_grade_summary.* import kotlinx.android.synthetic.main.item_grade_summary.*
class GradeSummaryItem(header: GradeSummaryHeader, private val grade: String, private val title: String) class GradeSummaryItem(header: GradeSummaryHeader, private val grade: String, private val title: String) :
: AbstractSectionableItem<GradeSummaryItem.ViewHolder, GradeSummaryHeader>(header) { AbstractSectionableItem<GradeSummaryItem.ViewHolder, GradeSummaryHeader>(header) {
override fun getLayoutRes() = R.layout.item_grade_summary 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) return ViewHolder(view, adapter)
} }
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder?, override fun bindViewHolder(
position: Int, payloads: MutableList<Any>?) { adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder?,
position: Int, payloads: MutableList<Any>?
) {
holder?.run { holder?.run {
gradeSummaryItemGrade.text = grade gradeSummaryItemGrade.text = grade
gradeSummaryItemTitle.text = title gradeSummaryItemTitle.text = title
@ -46,9 +49,8 @@ class GradeSummaryItem(header: GradeSummaryHeader, private val grade: String, pr
return result return result
} }
class ViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?) : FlexibleViewHolder(view, adapter),
class ViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?) LayoutContainer {
: FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View? override val containerView: View?
get() = contentView get() = contentView

View File

@ -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.about.AboutModule
import io.github.wulkanowy.ui.modules.account.AccountDialog import io.github.wulkanowy.ui.modules.account.AccountDialog
import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment 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.exam.ExamFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment
import io.github.wulkanowy.ui.modules.grade.GradeModule import io.github.wulkanowy.ui.modules.grade.GradeModule
@ -41,6 +42,10 @@ abstract class MainModule {
@ContributesAndroidInjector @ContributesAndroidInjector
abstract fun bindAttendanceFragment(): AttendanceFragment abstract fun bindAttendanceFragment(): AttendanceFragment
@PerFragment
@ContributesAndroidInjector
abstract fun bindAttendanceSummaryFragment(): AttendanceSummaryFragment
@PerFragment @PerFragment
@ContributesAndroidInjector @ContributesAndroidInjector
abstract fun bindExamFragment(): ExamFragment abstract fun bindExamFragment(): ExamFragment

View File

@ -72,7 +72,7 @@ class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.Mai
get() { get() {
return context?.run { return context?.run {
getString(R.string.about_title) to getString(R.string.about_title) to
ContextCompat.getDrawable(this, R.drawable.ic_more_about_24dp) ContextCompat.getDrawable(this, R.drawable.ic_all_about_24dp)
} }
} }

View File

@ -79,7 +79,7 @@ class TimetableFragment : BaseFragment(), TimetableView, MainView.MainChildView,
} }
override fun resetView() { override fun resetView() {
timetableAdapter.smoothScrollToPosition(0) timetableRecycler.smoothScrollToPosition(0)
} }
override fun onFragmentReselected() { override fun onFragmentReselected() {

View File

@ -58,7 +58,7 @@ class TimetablePresenter @Inject constructor(
if (currentDate != it) { if (currentDate != it) {
loadData(it) loadData(it)
reloadView() reloadView()
} else view?.resetView() } else if (view?.isViewEmpty == false) view?.resetView()
} }
} }

View File

@ -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
}

View File

@ -5,6 +5,7 @@ import com.crashlytics.android.answers.CustomEvent
import com.crashlytics.android.answers.LoginEvent import com.crashlytics.android.answers.LoginEvent
import com.crashlytics.android.answers.SignUpEvent import com.crashlytics.android.answers.SignUpEvent
import timber.log.Timber import timber.log.Timber
import kotlin.math.min
fun logLogin(method: String) { fun logLogin(method: String) {
try { try {
@ -20,7 +21,7 @@ fun logRegister(message: String, result: Boolean, symbol: String, endpoint: Stri
.putMethod("Login activity") .putMethod("Login activity")
.putSuccess(result) .putSuccess(result)
.putCustomAttribute("symbol", symbol) .putCustomAttribute("symbol", symbol)
.putCustomAttribute("message", message) .putCustomAttribute("message", message.substring(0, min(message.length, 100)))
.putCustomAttribute("endpoint", endpoint) .putCustomAttribute("endpoint", endpoint)
) )
} catch (e: Throwable) { } catch (e: Throwable) {

View File

@ -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)
}
}
}

View File

@ -1,15 +1,22 @@
package io.github.wulkanowy.utils package io.github.wulkanowy.utils
import org.threeten.bp.DateTimeUtils import org.threeten.bp.DayOfWeek.FRIDAY
import org.threeten.bp.DayOfWeek.* 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.Instant
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDateTime import org.threeten.bp.LocalDateTime
import org.threeten.bp.Month
import org.threeten.bp.ZoneId import org.threeten.bp.ZoneId
import org.threeten.bp.format.DateTimeFormatter import org.threeten.bp.format.DateTimeFormatter
import org.threeten.bp.format.DateTimeFormatter.ofPattern import org.threeten.bp.format.DateTimeFormatter.ofPattern
import org.threeten.bp.temporal.TemporalAdjusters.* import org.threeten.bp.format.TextStyle.FULL_STANDALONE
import java.util.* 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" 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)) 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 inline val LocalDate.nextSchoolDay: LocalDate
get() { get() {
return when (this.dayOfWeek) { 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) * [Dz.U. 2016 poz. 1335](http://prawo.sejm.gov.pl/isap.nsf/DocDetails.xsp?id=WDU20160001335)
*/ */
inline val LocalDate.isHolidays: Boolean inline val LocalDate.isHolidays: Boolean
get() { get() {
return LocalDate.of(this.year, 9, 1).run { return LocalDate.of(this.year, 9, 1).run {

View File

@ -4,9 +4,9 @@
android:viewportHeight="24" android:viewportHeight="24"
android:viewportWidth="24"> android:viewportWidth="24">
<path <path
android:fillColor="#000" android:fillColor="#FFFFFFFF"
android:pathData="M11,7h2v2h-2zM11,11h2v6h-2z" /> android:pathData="M11,7h2v2h-2zM11,11h2v6h-2z" />
<path <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" /> 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> </vector>

View 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>

View 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>

View 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" />

View File

@ -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>

View 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>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/gradeMenuSemester" android:id="@+id/gradeMenuSemester"
android:icon="@drawable/ic_menu_grade_semester_24dp" android:icon="@drawable/ic_menu_grade_semester_24dp"

View File

@ -118,6 +118,8 @@
<item quantity="many">%1$d nieobecności</item> <item quantity="many">%1$d nieobecności</item>
</plurals> </plurals>
<!--Attendance summary-->
<string name="attendance_summary_final">Frekwencja</string>
<!--Exam--> <!--Exam-->
<string name="exam_no_items">Brak sprawdzianów w tym tygodniu</string> <string name="exam_no_items">Brak sprawdzianów w tym tygodniu</string>

View File

@ -108,6 +108,9 @@
<item quantity="other">%1$d absences</item> <item quantity="other">%1$d absences</item>
</plurals> </plurals>
<!--Attendance summary-->
<string name="attendance_summary_final">Attendance</string>
<!--Exam--> <!--Exam-->
<string name="exam_no_items">No exams in this week</string> <string name="exam_no_items">No exams in this week</string>

View File

@ -1,10 +1,13 @@
package io.github.wulkanowy.utils 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.junit.Test
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDateTime import org.threeten.bp.LocalDateTime
import java.util.* import org.threeten.bp.Month.JANUARY
import java.util.Locale
class TimeExtensionTest { class TimeExtensionTest {
@ -43,6 +46,14 @@ class TimeExtensionTest {
assertEquals(LocalDate.of(2018, 10, 12), LocalDate.of(2018, 10, 8).friday) 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 @Test
fun weekDayNameTest() { fun weekDayNameTest() {
Locale.setDefault(Locale.forLanguageTag("PL")) Locale.setDefault(Locale.forLanguageTag("PL"))
@ -74,7 +85,6 @@ class TimeExtensionTest {
assertEquals(LocalDate.of(2018, 10, 3), LocalDate.of(2018, 10, 4).previousSchoolDay) assertEquals(LocalDate.of(2018, 10, 3), LocalDate.of(2018, 10, 4).previousSchoolDay)
} }
@Test @Test
fun nextOrSameSchoolDayTest() { fun nextOrSameSchoolDayTest() {
assertEquals(LocalDate.of(2018, 9, 28), LocalDate.of(2018, 9, 28).nextOrSameSchoolDay) assertEquals(LocalDate.of(2018, 9, 28), LocalDate.of(2018, 9, 28).nextOrSameSchoolDay)