Refactor attendance module (#161)

This commit is contained in:
Mikołaj Pich 2018-10-01 19:41:09 +02:00 committed by Rafał Borcz
parent a1f64baca4
commit 357b2350cb
35 changed files with 1151 additions and 571 deletions

View File

@ -68,12 +68,13 @@ play {
uploadImages = true uploadImages = true
} }
ext.supportVersion = "28.0.0-rc02" ext.supportVersion = "28.0.0"
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'com.github.wulkanowy:api:ad57669' implementation('com.github.wulkanowy:api:07201a4') {
exclude module: "threetenbp"
}
implementation "com.android.support:support-v4:$supportVersion" implementation "com.android.support:support-v4:$supportVersion"
implementation "com.android.support:design:$supportVersion" implementation "com.android.support:design:$supportVersion"
implementation "com.android.support:cardview-v7:$supportVersion" implementation "com.android.support:cardview-v7:$supportVersion"
@ -116,6 +117,7 @@ dependencies {
debugImplementation "com.amitshekhar.android:debug-db:1.0.4" debugImplementation "com.amitshekhar.android:debug-db:1.0.4"
testImplementation "junit:junit:4.12" testImplementation "junit:junit:4.12"
testImplementation "io.mockk:mockk:1.8.8"
testImplementation "org.mockito:mockito-inline:2.21.0" testImplementation "org.mockito:mockito-inline:2.21.0"
androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test:runner:1.0.2'

View File

@ -0,0 +1,52 @@
package io.github.wulkanowy.data.repositories.local
import android.arch.persistence.room.Room
import android.support.test.InstrumentationRegistry
import android.support.test.runner.AndroidJUnit4
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.Semester
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.threeten.bp.LocalDate
import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
class AttendanceLocalTest {
private lateinit var attendanceLocal: AttendanceLocal
private lateinit var testDb: AppDatabase
@Before
fun createDb() {
testDb = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(), AppDatabase::class.java).build()
attendanceLocal = AttendanceLocal(testDb.attendanceDao())
}
@After
fun closeDb() {
testDb.close()
}
@Test
fun saveAndReadTest() {
attendanceLocal.saveAttendance(listOf(
Attendance(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 10)),
Attendance(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 14)),
Attendance(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 17)) // in next week
))
val attendance = attendanceLocal
.getAttendance(Semester(studentId = "1", diaryId = "2", semesterId = "3"),
LocalDate.of(2018, 9, 10),
LocalDate.of(2018, 9, 14)
)
.blockingGet()
assertEquals(2, attendance.size)
assertEquals(attendance[0].date, LocalDate.of(2018, 9, 10))
assertEquals(attendance[1].date, LocalDate.of(2018, 9, 14))
}
}

View File

@ -11,7 +11,6 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import java.sql.Date
import kotlin.test.assertEquals import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@ -35,16 +34,19 @@ class ExamLocalTest {
@Test @Test
fun saveAndReadTest() { fun saveAndReadTest() {
examLocal.saveExams(listOf( examLocal.saveExams(listOf(
Exam(studentId = "1", diaryId = "2", date = Date.valueOf("2018-09-10")), Exam(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 10)),
Exam(studentId = "1", diaryId = "2", date = Date.valueOf("2018-09-14")), Exam(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 14)),
Exam(studentId = "1", diaryId = "2", date = Date.valueOf("2018-09-17")) // in next week Exam(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 17)) // in next week
)) ))
val exams = examLocal val exams = examLocal
.getExams(Semester(studentId = "1", diaryId = "2", semesterId = "3"), LocalDate.of(2018, 9, 10)) .getExams(Semester(studentId = "1", diaryId = "2", semesterId = "3"),
LocalDate.of(2018, 9, 10),
LocalDate.of(2018, 9, 14)
)
.blockingGet() .blockingGet()
assertEquals(2, exams.size) assertEquals(2, exams.size)
assertEquals(exams[0].date, Date.valueOf("2018-09-10")) assertEquals(exams[0].date, LocalDate.of(2018, 9, 10))
assertEquals(exams[1].date, Date.valueOf("2018-09-14")) assertEquals(exams[1].date, LocalDate.of(2018, 9, 14))
} }
} }

View File

@ -52,4 +52,8 @@ internal class RepositoryModule {
@Singleton @Singleton
@Provides @Provides
fun provideExamDao(database: AppDatabase) = database.examsDao() fun provideExamDao(database: AppDatabase) = database.examsDao()
@Singleton
@Provides
fun provideAttendanceDao(database: AppDatabase) = database.attendanceDao()
} }

View File

@ -3,9 +3,11 @@ package io.github.wulkanowy.data.db
import android.arch.persistence.room.Database import android.arch.persistence.room.Database
import android.arch.persistence.room.RoomDatabase import android.arch.persistence.room.RoomDatabase
import android.arch.persistence.room.TypeConverters import android.arch.persistence.room.TypeConverters
import io.github.wulkanowy.data.db.dao.AttendanceDao
import io.github.wulkanowy.data.db.dao.ExamDao import io.github.wulkanowy.data.db.dao.ExamDao
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.entities.Attendance
import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Exam
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
@ -16,7 +18,8 @@ import javax.inject.Singleton
entities = [ entities = [
Student::class, Student::class,
Semester::class, Semester::class,
Exam::class Exam::class,
Attendance::class
], ],
version = 1, version = 1,
exportSchema = false exportSchema = false
@ -29,4 +32,6 @@ abstract class AppDatabase : RoomDatabase() {
abstract fun semesterDao(): SemesterDao abstract fun semesterDao(): SemesterDao
abstract fun examsDao(): ExamDao abstract fun examsDao(): ExamDao
abstract fun attendanceDao(): AttendanceDao
} }

View File

@ -1,14 +1,18 @@
package io.github.wulkanowy.data.db package io.github.wulkanowy.data.db
import android.arch.persistence.room.TypeConverter import android.arch.persistence.room.TypeConverter
import org.threeten.bp.*
import java.util.* import java.util.*
class Converters { class Converters {
@TypeConverter @TypeConverter
fun fromTimestamp(value: Long?): Date? = value?.run { Date(value) } fun fromTimestamp(value: Long?): LocalDate? = value?.run {
DateTimeUtils.toInstant(Date(value)).atZone(ZoneOffset.UTC).toLocalDate()
}
@TypeConverter @TypeConverter
fun dateToTimestamp(date: Date?): Long? = date?.time fun dateToTimestamp(date: LocalDate?): Long? {
return date?.atStartOfDay()?.toInstant(ZoneOffset.UTC)?.toEpochMilli()
}
} }

View File

@ -0,0 +1,22 @@
package io.github.wulkanowy.data.db.dao
import android.arch.persistence.room.Dao
import android.arch.persistence.room.Delete
import android.arch.persistence.room.Insert
import android.arch.persistence.room.Query
import io.github.wulkanowy.data.db.entities.Attendance
import io.reactivex.Maybe
import org.threeten.bp.LocalDate
@Dao
interface AttendanceDao {
@Insert
fun insertAll(exams: List<Attendance>): List<Long>
@Delete
fun deleteAll(exams: List<Attendance>)
@Query("SELECT * FROM Attendance WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
fun getExams(diaryId: String, studentId: String, from: LocalDate, end: LocalDate): Maybe<List<Attendance>>
}

View File

@ -6,7 +6,7 @@ import android.arch.persistence.room.Insert
import android.arch.persistence.room.Query import android.arch.persistence.room.Query
import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Exam
import io.reactivex.Maybe import io.reactivex.Maybe
import java.util.* import org.threeten.bp.LocalDate
@Dao @Dao
interface ExamDao { interface ExamDao {
@ -18,5 +18,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 getExams(diaryId: String, studentId: String, from: Date, end: Date): Maybe<List<Exam>> fun getExams(diaryId: String, studentId: String, from: LocalDate, end: LocalDate): Maybe<List<Exam>>
} }

View File

@ -0,0 +1,40 @@
package io.github.wulkanowy.data.db.entities
import android.arch.persistence.room.ColumnInfo
import android.arch.persistence.room.Entity
import android.arch.persistence.room.PrimaryKey
import org.threeten.bp.LocalDate
import java.io.Serializable
@Entity(tableName = "Attendance")
data class Attendance(
@PrimaryKey(autoGenerate = true)
var id: Long = 0,
@ColumnInfo(name = "student_id")
var studentId: String = "",
@ColumnInfo(name = "diary_id")
var diaryId: String = "",
var date: LocalDate,
var number: Int = 0,
var subject: String = "",
var name: String = "",
var presence: Boolean = false,
var absence: Boolean = false,
var exemption: Boolean = false,
var lateness: Boolean = false,
var excused: Boolean = false,
var deleted: Boolean = false
) : Serializable

View File

@ -3,8 +3,8 @@ package io.github.wulkanowy.data.db.entities
import android.arch.persistence.room.ColumnInfo import android.arch.persistence.room.ColumnInfo
import android.arch.persistence.room.Entity import android.arch.persistence.room.Entity
import android.arch.persistence.room.PrimaryKey import android.arch.persistence.room.PrimaryKey
import org.threeten.bp.LocalDate
import java.io.Serializable import java.io.Serializable
import java.util.*
@Entity(tableName = "Exams") @Entity(tableName = "Exams")
data class Exam( data class Exam(
@ -18,10 +18,10 @@ data class Exam(
@ColumnInfo(name = "diary_id") @ColumnInfo(name = "diary_id")
var diaryId: String = "", var diaryId: String = "",
var date: Date, var date: LocalDate,
@ColumnInfo(name = "entry_date") @ColumnInfo(name = "entry_date")
var entryDate: Date = Date(), var entryDate: LocalDate = LocalDate.now(),
var subject: String = "", var subject: String = "",

View File

@ -0,0 +1,43 @@
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.Attendance
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.repositories.local.AttendanceLocal
import io.github.wulkanowy.data.repositories.remote.AttendanceRemote
import io.github.wulkanowy.utils.extension.getWeekFirstDayAlwaysCurrent
import io.reactivex.Single
import org.threeten.bp.DayOfWeek
import org.threeten.bp.LocalDate
import org.threeten.bp.temporal.TemporalAdjusters
import java.net.UnknownHostException
import javax.inject.Inject
class AttendanceRepository @Inject constructor(
private val settings: InternetObservingSettings,
private val local: AttendanceLocal,
private val remote: AttendanceRemote
) {
fun getAttendance(semester: Semester, startDate: LocalDate, endDate: LocalDate, forceRefresh: Boolean = false): Single<List<Attendance>> {
val start = startDate.getWeekFirstDayAlwaysCurrent()
val end = endDate.with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY))
return local.getAttendance(semester, start, end).filter { !forceRefresh }
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings).flatMap {
if (it) remote.getAttendance(semester, start, end)
else Single.error(UnknownHostException())
}.flatMap { newLessons ->
local.getAttendance(semester, start, end).toSingle(emptyList()).map { grades ->
local.deleteAttendance(grades - newLessons)
local.saveAttendance(newLessons - grades)
newLessons
}
}).map { list ->
list.asSequence().filter {
it.date in startDate..endDate
}.toList()
}
}
}

View File

@ -6,8 +6,12 @@ import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.repositories.local.ExamLocal import io.github.wulkanowy.data.repositories.local.ExamLocal
import io.github.wulkanowy.data.repositories.remote.ExamRemote import io.github.wulkanowy.data.repositories.remote.ExamRemote
import io.github.wulkanowy.utils.extension.getWeekFirstDayAlwaysCurrent
import io.github.wulkanowy.utils.extension.toDate
import io.reactivex.Single import io.reactivex.Single
import org.threeten.bp.DayOfWeek
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import org.threeten.bp.temporal.TemporalAdjusters
import java.net.UnknownHostException import java.net.UnknownHostException
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -19,21 +23,24 @@ class ExamRepository @Inject constructor(
private val remote: ExamRemote private val remote: ExamRemote
) { ) {
fun getExams(semester: Semester, date: LocalDate, forceRefresh: Boolean = false): Single<List<Exam>> { fun getExams(semester: Semester, startDate: LocalDate, endDate: LocalDate, forceRefresh: Boolean = false): Single<List<Exam>> {
return local.getExams(semester, date).filter { !forceRefresh } val start = startDate.getWeekFirstDayAlwaysCurrent()
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings) val end = endDate.with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY))
.flatMap {
if (it) remote.getExams(semester, date)
else Single.error(UnknownHostException())
}.flatMap { newExams ->
local.getExams(semester, date).toSingle(emptyList())
.map {
local.deleteExams(it - newExams)
local.saveExams(newExams - it)
newExams return local.getExams(semester, start, end).filter { !forceRefresh }
} .switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings).flatMap {
} if (it) remote.getExams(semester, start, end)
) else Single.error(UnknownHostException())
}.flatMap { newExams ->
local.getExams(semester, start, end).toSingle(emptyList()).map { grades ->
local.deleteExams(grades - newExams)
local.saveExams(newExams - grades)
newExams
}
}).map { list ->
list.asSequence().filter {
it.date in startDate..endDate
}.toList()
}
} }
} }

View File

@ -0,0 +1,24 @@
package io.github.wulkanowy.data.repositories.local
import io.github.wulkanowy.data.db.dao.AttendanceDao
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.Semester
import io.reactivex.Maybe
import org.threeten.bp.LocalDate
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.getExams(semester.diaryId, semester.studentId, startDate, endDate)
.filter { !it.isEmpty() }
}
fun saveAttendance(attendance: List<Attendance>) {
attendanceDb.insertAll(attendance)
}
fun deleteAttendance(attendance: List<Attendance>) {
attendanceDb.deleteAll(attendance)
}
}

View File

@ -12,10 +12,9 @@ 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): Maybe<List<Exam>> { fun getExams(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe<List<Exam>> {
return examDb.getExams(semester.diaryId, semester.studentId, startDate.toDate(), return examDb.getExams(semester.diaryId, semester.studentId, startDate, endDate)
startDate.with(TemporalAdjusters.next(DayOfWeek.FRIDAY)).toDate() .filter { !it.isEmpty() }
).filter { !it.isEmpty() }
} }
fun saveExams(exams: List<Exam>) { fun saveExams(exams: List<Exam>) {

View File

@ -0,0 +1,38 @@
package io.github.wulkanowy.data.repositories.remote
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.utils.extension.toLocalDate
import io.reactivex.Single
import org.threeten.bp.LocalDate
import javax.inject.Inject
class AttendanceRemote @Inject constructor(private val api: Api) {
fun getAttendance(semester: Semester, startDate: LocalDate, endDate: LocalDate): Single<List<Attendance>> {
return Single.just(api.run {
if (diaryId != semester.diaryId) {
diaryId = semester.diaryId
notifyDataChanged()
}
}).flatMap { api.getAttendance(startDate, endDate) }.map { attendance ->
attendance.map {
Attendance(
studentId = semester.studentId,
diaryId = semester.diaryId,
date = it.date.toLocalDate(),
number = it.number,
subject = it.subject,
name = it.name,
presence = it.presence,
absence = it.absence,
exemption = it.exemption,
lateness = it.lateness,
excused = it.excused,
deleted = it.deleted
)
}
}
}
}

View File

@ -3,35 +3,34 @@ package io.github.wulkanowy.data.repositories.remote
import io.github.wulkanowy.api.Api import io.github.wulkanowy.api.Api
import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.utils.extension.toDate import io.github.wulkanowy.utils.extension.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
class ExamRemote @Inject constructor(private val api: Api) { class ExamRemote @Inject constructor(private val api: Api) {
fun getExams(semester: Semester, startDate: LocalDate): Single<List<Exam>> { fun getExams(semester: Semester, startDate: LocalDate, endDate: LocalDate): Single<List<Exam>> {
return Single.just(api.run { return Single.just(api.run {
if (diaryId != semester.diaryId) { if (diaryId != semester.diaryId) {
diaryId = semester.diaryId diaryId = semester.diaryId
notifyDataChanged() notifyDataChanged()
} }
}).flatMap { api.getExams(startDate.toDate()) } }).flatMap { api.getExams(startDate, endDate) }.map { exams ->
.map { exams -> exams.map {
exams.map { Exam(
Exam( studentId = semester.studentId,
studentId = semester.studentId, diaryId = semester.diaryId,
diaryId = semester.diaryId, date = it.date.toLocalDate(),
date = it.date, entryDate = it.entryDate.toLocalDate(),
entryDate = it.entryDate, subject = it.subject,
subject = it.subject, group = it.group,
group = it.group, type = it.type,
type = it.type, description = it.description,
description = it.description, teacher = it.teacher,
teacher = it.teacher, teacherSymbol = it.teacherSymbol
teacherSymbol = it.teacherSymbol )
) }
} }
}
} }
} }

View File

@ -0,0 +1,49 @@
package io.github.wulkanowy.ui.main.attendance
import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.utils.extension.toFormat
import kotlinx.android.synthetic.main.dialog_attendance.*
class AttendanceDialog : DialogFragment() {
private lateinit var attendance: Attendance
companion object {
private const val ARGUMENT_KEY = "Item"
fun newInstance(exam: Attendance): AttendanceDialog {
return AttendanceDialog().apply {
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(DialogFragment.STYLE_NORMAL, R.style.DialogFragmentTheme)
arguments?.run {
attendance = getSerializable(ARGUMENT_KEY) as Attendance
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
dialog.setTitle(getString(R.string.all_details))
return inflater.inflate(R.layout.dialog_attendance, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
attendanceDialogSubject.text = attendance.subject
attendanceDialogDescription.text = attendance.name
attendanceDialogDate.text = attendance.date.toFormat()
attendanceDialogNumber.text = attendance.number.toString()
attendanceDialogClose.setOnClickListener { dismiss() }
}
}

View File

@ -4,17 +4,111 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
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.common.SmoothScrollLinearLayoutManager
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.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.utils.extension.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_attendance.*
import javax.inject.Inject
class AttendanceFragment : BaseFragment() { class AttendanceFragment : BaseFragment(), AttendanceView {
@Inject
lateinit var presenter: AttendancePresenter
@Inject
lateinit var attendanceAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
companion object { companion object {
private const val SAVED_DATE_KEY = "CURRENT_DATE"
fun newInstance() = AttendanceFragment() fun newInstance() = AttendanceFragment()
} }
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)
} }
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
presenter.run {
attachView(this@AttendanceFragment)
loadData(date = savedInstanceState?.getLong(SAVED_DATE_KEY))
}
}
override fun initView() {
attendanceAdapter.run {
isAutoCollapseOnExpand = true
isAutoScrollOnExpand = true
setOnItemClickListener { presenter.onAttendanceItemSelected(getItem(it))}
}
attendanceRecycler.run {
layoutManager = SmoothScrollLinearLayoutManager(context)
adapter = attendanceAdapter
}
attendanceSwipe.setOnRefreshListener { presenter.loadData(date = null, forceRefresh = true) }
attendancePreviousButton.setOnClickListener { presenter.loadAttendanceForPreviousDay() }
attendanceNextButton.setOnClickListener { presenter.loadAttendanceForNextDay() }
}
override fun updateData(data: List<AttendanceItem>) {
attendanceAdapter.updateDataSet(data, true)
}
override fun clearData() {
attendanceAdapter.clear()
}
override fun updateNavigationDay(date: String) {
attendanceNavDate.text = date
}
override fun isViewEmpty() = attendanceAdapter.isEmpty
override fun showEmpty(show: Boolean) {
attendanceEmpty.visibility = if (show) View.VISIBLE else View.GONE
}
override fun showProgress(show: Boolean) {
attendanceProgress.visibility = if (show) View.VISIBLE else View.GONE
}
override fun showContent(show: Boolean) {
attendanceRecycler.visibility = if (show) View.VISIBLE else View.GONE
}
override fun showRefresh(show: Boolean) {
attendanceSwipe.isRefreshing = show
}
override fun showPreButton(show: Boolean) {
attendancePreviousButton.visibility = if (show) View.VISIBLE else View.INVISIBLE
}
override fun showNextButton(show: Boolean) {
attendanceNextButton.visibility = if (show) View.VISIBLE else View.INVISIBLE
}
override fun showAttendanceDialog(lesson: Attendance) {
AttendanceDialog.newInstance(lesson).show(fragmentManager, lesson.toString())
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay())
}
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
presenter.loadData(date = savedInstanceState?.getLong(SAVED_DATE_KEY))
}
override fun onDestroyView() {
super.onDestroyView()
presenter.detachView()
}
} }

View File

@ -0,0 +1,55 @@
package io.github.wulkanowy.ui.main.attendance
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 io.github.wulkanowy.data.db.entities.Attendance
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_attendance.*
class AttendanceItem : AbstractFlexibleItem<AttendanceItem.ViewHolder>() {
lateinit var attendance: Attendance
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
return ViewHolder(view, adapter)
}
override fun getLayoutRes(): Int = R.layout.item_attendance
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as AttendanceItem
if (attendance != other.attendance) return false
return true
}
override fun hashCode(): Int {
return attendance.hashCode()
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder,
position: Int, payloads: MutableList<Any>?) {
holder.bind(attendance)
}
class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter),
LayoutContainer {
override val containerView: View
get() = contentView
fun bind(lesson: Attendance) {
attendanceItemNumber.text = lesson.number.toString()
attendanceItemSubject.text = lesson.subject
attendanceItemDescription.text = lesson.name
attendanceItemAlert.visibility = if (lesson.absence && !lesson.excused) View.VISIBLE else View.INVISIBLE
}
}
}

View File

@ -0,0 +1,96 @@
package io.github.wulkanowy.ui.main.attendance
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.repositories.AttendanceRepository
import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.extension.*
import io.github.wulkanowy.utils.schedulers.SchedulersManager
import org.threeten.bp.LocalDate
import javax.inject.Inject
class AttendancePresenter @Inject constructor(
private val errorHandler: ErrorHandler,
private val schedulers: SchedulersManager,
private val attendanceRepository: AttendanceRepository,
private val sessionRepository: SessionRepository
) : BasePresenter<AttendanceView>(errorHandler) {
var currentDate: LocalDate = LocalDate.now().getNearSchoolDayPrevOnWeekEnd()
private set
override fun attachView(view: AttendanceView) {
super.attachView(view)
view.initView()
}
fun loadAttendanceForPreviousDay() = loadData(currentDate.getPreviousWorkDay().toEpochDay())
fun loadAttendanceForNextDay() = loadData(currentDate.getNextWorkDay().toEpochDay())
fun loadData(date: Long?, forceRefresh: Boolean = false) {
this.currentDate = LocalDate.ofEpochDay(date ?: currentDate.getNearSchoolDayPrevOnWeekEnd().toEpochDay())
if (currentDate.isHolidays()) return
disposable.clear()
disposable.add(sessionRepository.getSemesters()
.map { selectSemester(it, -1) }
.flatMap { attendanceRepository.getAttendance(it, currentDate, currentDate, forceRefresh) }
.map { createTimetableItems(it) }
.subscribeOn(schedulers.backgroundThread())
.observeOn(schedulers.mainThread())
.doOnSubscribe {
view?.run {
showRefresh(forceRefresh)
showProgress(!forceRefresh)
if (!forceRefresh) {
showEmpty(false)
clearData()
}
showPreButton(!currentDate.minusDays(1).isHolidays())
showNextButton(!currentDate.plusDays(1).isHolidays())
updateNavigationDay(currentDate.toFormat("EEEE \n dd.MM.YYYY").capitalize())
}
}
.doFinally {
view?.run {
showRefresh(false)
showProgress(false)
}
}
.subscribe({
view?.run {
showEmpty(it.isEmpty())
showContent(it.isNotEmpty())
updateData(it)
}
}) {
view?.run { showEmpty(isViewEmpty()) }
errorHandler.proceed(it)
})
}
private fun createTimetableItems(items: List<Attendance>): List<AttendanceItem> {
return items.map {
AttendanceItem().apply { attendance = it }
}
}
fun onAttendanceItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is AttendanceItem) view?.showAttendanceDialog(item.attendance)
}
private fun selectSemester(semesters: List<Semester>, index: Int): Semester {
return semesters.single { it.current }.let { currentSemester ->
if (index == -1) currentSemester
else semesters.single { semester ->
semester.run {
semesterName - 1 == index && diaryId == currentSemester.diaryId
}
}
}
}
}

View File

@ -0,0 +1,31 @@
package io.github.wulkanowy.ui.main.attendance
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.ui.base.BaseView
interface AttendanceView : BaseView {
fun initView()
fun updateData(data: List<AttendanceItem>)
fun clearData()
fun updateNavigationDay(date: String)
fun isViewEmpty(): Boolean
fun showEmpty(show: Boolean)
fun showProgress(show: Boolean)
fun showContent(show: Boolean)
fun showRefresh(show: Boolean)
fun showPreButton(show: Boolean)
fun showNextButton(show: Boolean)
fun showAttendanceDialog(lesson: Attendance)
}

View File

@ -10,12 +10,11 @@ import io.github.wulkanowy.utils.extension.getWeekDayName
import io.github.wulkanowy.utils.extension.toFormat import io.github.wulkanowy.utils.extension.toFormat
import kotlinx.android.extensions.LayoutContainer import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.header_exam.* import kotlinx.android.synthetic.main.header_exam.*
import org.apache.commons.lang3.StringUtils import org.threeten.bp.LocalDate
import java.util.*
class ExamHeader : AbstractHeaderItem<ExamHeader.ViewHolder>() { class ExamHeader : AbstractHeaderItem<ExamHeader.ViewHolder>() {
lateinit var date: Date lateinit var date: LocalDate
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?): ViewHolder { override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?): ViewHolder {
return ViewHolder(view, adapter) return ViewHolder(view, adapter)
@ -41,7 +40,7 @@ class ExamHeader : AbstractHeaderItem<ExamHeader.ViewHolder>() {
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder, override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder,
position: Int, payloads: MutableList<Any>?) { position: Int, payloads: MutableList<Any>?) {
holder.run { holder.run {
examHeaderDay.text = StringUtils.capitalize(date.getWeekDayName()) examHeaderDay.text = date.getWeekDayName().capitalize()
examHeaderDate.text = date.toFormat() examHeaderDate.text = date.toFormat()
} }
} }

View File

@ -7,12 +7,11 @@ import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.repositories.ExamRepository import io.github.wulkanowy.data.repositories.ExamRepository
import io.github.wulkanowy.data.repositories.SessionRepository import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.extension.getWeekFirstDayNextOnWeekEnd
import io.github.wulkanowy.utils.extension.isHolidays import io.github.wulkanowy.utils.extension.isHolidays
import io.github.wulkanowy.utils.extension.toFormat import io.github.wulkanowy.utils.extension.toFormat
import io.github.wulkanowy.utils.getNearMonday
import io.github.wulkanowy.utils.schedulers.SchedulersManager import io.github.wulkanowy.utils.schedulers.SchedulersManager
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import java.util.*
import javax.inject.Inject import javax.inject.Inject
class ExamPresenter @Inject constructor( class ExamPresenter @Inject constructor(
@ -22,7 +21,7 @@ class ExamPresenter @Inject constructor(
private val sessionRepository: SessionRepository private val sessionRepository: SessionRepository
) : BasePresenter<ExamView>(errorHandler) { ) : BasePresenter<ExamView>(errorHandler) {
var currentDate: LocalDate = getNearMonday(LocalDate.now()) var currentDate: LocalDate = LocalDate.now().getWeekFirstDayNextOnWeekEnd()
private set private set
override fun attachView(view: ExamView) { override fun attachView(view: ExamView) {
@ -35,13 +34,13 @@ class ExamPresenter @Inject constructor(
fun loadExamsForNextWeek() = loadData(currentDate.plusDays(7).toEpochDay()) fun loadExamsForNextWeek() = loadData(currentDate.plusDays(7).toEpochDay())
fun loadData(date: Long?, forceRefresh: Boolean = false) { fun loadData(date: Long?, forceRefresh: Boolean = false) {
this.currentDate = LocalDate.ofEpochDay(date ?: getNearMonday(currentDate).toEpochDay()) this.currentDate = LocalDate.ofEpochDay(date ?: currentDate.getWeekFirstDayNextOnWeekEnd().toEpochDay())
if (currentDate.isHolidays()) return if (currentDate.isHolidays()) return
disposable.clear() disposable.clear()
disposable.add(sessionRepository.getSemesters() disposable.add(sessionRepository.getSemesters()
.map { selectSemester(it, -1) } .map { selectSemester(it, -1) }
.flatMap { examRepository.getExams(it, currentDate, forceRefresh) } .flatMap { examRepository.getExams(it, currentDate, currentDate.plusDays(4), forceRefresh) }
.map { it.groupBy { exam -> exam.date }.toSortedMap() } .map { it.groupBy { exam -> exam.date }.toSortedMap() }
.map { createExamItems(it) } .map { createExamItems(it) }
.subscribeOn(schedulers.backgroundThread()) .subscribeOn(schedulers.backgroundThread())
@ -72,7 +71,7 @@ class ExamPresenter @Inject constructor(
.subscribe({ view?.updateData(it) }) { errorHandler.proceed(it) }) .subscribe({ view?.updateData(it) }) { errorHandler.proceed(it) })
} }
private fun createExamItems(items: Map<Date, List<Exam>>): List<ExamItem> { private fun createExamItems(items: Map<LocalDate, List<Exam>>): List<ExamItem> {
return items.flatMap { return items.flatMap {
val header = ExamHeader().apply { date = it.key } val header = ExamHeader().apply { date = it.key }
it.value.reversed().map { item -> it.value.reversed().map { item ->

View File

@ -1,104 +0,0 @@
package io.github.wulkanowy.utils
import org.threeten.bp.DayOfWeek.*
import org.threeten.bp.LocalDate
import org.threeten.bp.Year
import org.threeten.bp.format.DateTimeFormatter
import org.threeten.bp.temporal.TemporalAdjuster
import org.threeten.bp.temporal.TemporalAdjusters
import java.util.*
private val formatter = DateTimeFormatter.ofPattern(DATE_PATTERN)
fun getParsedDate(dateString: String, dateFormat: String): LocalDate {
return LocalDate.parse(dateString, DateTimeFormatter.ofPattern(dateFormat))
}
fun getMondaysFromCurrentSchoolYear() = getMondaysFromCurrentSchoolYear(LocalDate.now())
fun getMondaysFromCurrentSchoolYear(date: LocalDate): List<String> {
val startDate = getFirstSchoolDay(getSchoolYearForDate(date))
?.with(TemporalAdjusters.previousOrSame(MONDAY))
val endDate = getFirstSchoolDay(getSchoolYearForDate(date) + 1)
?.with(TemporalAdjusters.previousOrSame(MONDAY))
val dateList = ArrayList<String>()
var monday = startDate as LocalDate
while (monday.isBefore(endDate)) {
dateList.add(monday.format(formatter))
monday = monday.plusWeeks(1)
}
return dateList
}
fun getSchoolYearForDate(date: LocalDate): Int {
return if (date.monthValue <= 8) date.year - 1 else date.year
}
fun getFirstDayOfCurrentWeek(): String = getFirstDayOfCurrentWeek(LocalDate.now())
fun getFirstDayOfCurrentWeek(date: LocalDate): String {
return when (date.dayOfWeek) {
SATURDAY -> date.plusDays(2)
SUNDAY -> date.plusDays(1)
else -> date.with(MONDAY)
}.format(formatter)
}
fun getTodayOrNextDayOrder(next: Boolean): Int = getTodayOrNextDayOrder(next, LocalDate.now())
fun getTodayOrNextDayOrder(next: Boolean, date: LocalDate): Int {
val day = date.dayOfWeek
return if (next) {
if (day == SUNDAY) {
0
} else day.value
} else day.value - 1
}
fun getTodayOrNextDay(next: Boolean): String? = getTodayOrNextDay(next, LocalDate.now())
fun getTodayOrNextDay(next: Boolean, date: LocalDate): String? {
return (if (next) {
date.plusDays(1)
} else date).format(formatter)
}
fun isDateInWeek(firstWeekDay: LocalDate, date: LocalDate): Boolean {
return date.isAfter(firstWeekDay.minusDays(1)) && date.isBefore(firstWeekDay.plusDays(5))
}
fun getNearMonday(date: LocalDate): LocalDate {
return when(date.dayOfWeek) {
MONDAY -> date
SATURDAY, SUNDAY -> date.with(TemporalAdjusters.next(MONDAY))
else -> date.with(TemporalAdjusters.previous(MONDAY))
}
}
/**
* [Dz.U. 2016 poz. 1335](http://prawo.sejm.gov.pl/isap.nsf/DocDetails.xsp?id=WDU20160001335)
*/
fun isHolidays(): Boolean = isHolidays(LocalDate.now())
fun isHolidays(day: LocalDate): Boolean {
return day.isAfter(getLastSchoolDay(day.year)) && day.isBefore(getFirstSchoolDay(day.year))
}
fun getFirstSchoolDay(year: Int): LocalDate {
val firstSeptember = LocalDate.of(year, 9, 1)
return when (firstSeptember.dayOfWeek) {
FRIDAY,
SATURDAY,
SUNDAY -> firstSeptember.with(TemporalAdjusters.firstInMonth(MONDAY))
else -> firstSeptember
}
}
fun getLastSchoolDay(year: Int): LocalDate {
return LocalDate
.of(year, 6, 20)
.with(TemporalAdjusters.next(FRIDAY))
}

View File

@ -1,25 +0,0 @@
package io.github.wulkanowy.utils.extension
import io.github.wulkanowy.utils.DATE_PATTERN
import io.github.wulkanowy.utils.isHolidays
import org.threeten.bp.Instant
import org.threeten.bp.LocalDate
import org.threeten.bp.ZoneId
import org.threeten.bp.format.DateTimeFormatter
import java.util.*
private val formatter = DateTimeFormatter.ofPattern(DATE_PATTERN)
fun LocalDate.toDate(): Date = java.sql.Date.valueOf(this.format(formatter))
fun LocalDate.toFormat(format: String): String = this.format(DateTimeFormatter.ofPattern(format))
fun LocalDate.toFormat(): String = this.toFormat(DATE_PATTERN)
fun LocalDate.isHolidays(): Boolean = isHolidays(this)
fun Date.toLocalDate(): LocalDate = Instant.ofEpochMilli(this.time).atZone(ZoneId.systemDefault()).toLocalDate()
fun Date.getWeekDayName(): String = this.toLocalDate().format(DateTimeFormatter.ofPattern("EEEE", Locale.getDefault()))
fun Date.toFormat(): String = this.toLocalDate().toFormat()

View File

@ -0,0 +1,79 @@
package io.github.wulkanowy.utils.extension
import io.github.wulkanowy.utils.DATE_PATTERN
import org.threeten.bp.*
import org.threeten.bp.format.DateTimeFormatter
import org.threeten.bp.temporal.TemporalAdjusters
import java.text.SimpleDateFormat
import java.util.*
fun Date.toLocalDate(): LocalDate = LocalDate.parse(SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(this))
fun String.toDate(format: String = "yyyy-MM-dd"): LocalDate = LocalDate.parse(this, DateTimeFormatter.ofPattern(format))
fun LocalDate.toFormat(format: String): String = this.format(DateTimeFormatter.ofPattern(format))
fun LocalDate.toFormat(): String = this.toFormat(DATE_PATTERN)
fun LocalDate.getNextWorkDay(): LocalDate {
return when(this.dayOfWeek) {
DayOfWeek.FRIDAY, DayOfWeek.SATURDAY, DayOfWeek.SUNDAY -> this.with(TemporalAdjusters.next(DayOfWeek.MONDAY))
else -> this.plusDays(1)
}
}
fun LocalDate.getPreviousWorkDay(): LocalDate {
return when(this.dayOfWeek) {
DayOfWeek.SATURDAY, DayOfWeek.SUNDAY, DayOfWeek.MONDAY -> this.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY))
else -> this.minusDays(1)
}
}
fun LocalDate.getNearSchoolDayPrevOnWeekEnd(): LocalDate {
return when(this.dayOfWeek) {
DayOfWeek.SATURDAY, DayOfWeek.SUNDAY -> this.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY))
else -> this
}
}
fun LocalDate.getNearSchoolDayNextOnWeekEnd(): LocalDate {
return when(this.dayOfWeek) {
DayOfWeek.SATURDAY, DayOfWeek.SUNDAY -> this.with(TemporalAdjusters.next(DayOfWeek.MONDAY))
else -> this
}
}
fun LocalDate.getWeekDayName(): String = this.format(DateTimeFormatter.ofPattern("EEEE", Locale.getDefault()))
fun LocalDate.getWeekFirstDayAlwaysCurrent(): LocalDate {
return this.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
}
fun LocalDate.getWeekFirstDayNextOnWeekEnd(): LocalDate {
return when(this.dayOfWeek) {
DayOfWeek.SATURDAY, DayOfWeek.SUNDAY -> this.with(TemporalAdjusters.next(DayOfWeek.MONDAY))
else -> this.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
}
}
/**
* [Dz.U. 2016 poz. 1335](http://prawo.sejm.gov.pl/isap.nsf/DocDetails.xsp?id=WDU20160001335)
*/
fun LocalDate.isHolidays(): Boolean = this.isAfter(this.getLastSchoolDay()) && this.isBefore(this.getFirstSchoolDay())
fun LocalDate.getSchoolYear(): Int = if (this.monthValue <= 8) this.year - 1 else this.year
fun LocalDate.getFirstSchoolDay(): LocalDate {
return LocalDate.of(this.year, 9, 1).run {
when (dayOfWeek) {
DayOfWeek.FRIDAY, DayOfWeek.SATURDAY, DayOfWeek.SUNDAY -> with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY))
else -> this
}
}
}
fun LocalDate.getLastSchoolDay(): LocalDate {
return LocalDate
.of(this.year, 6, 20)
.with(TemporalAdjusters.next(DayOfWeek.FRIDAY))
}

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@ -8,146 +7,84 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minWidth="300dp" android:minWidth="300dp"
android:orientation="vertical"> android:orientation="vertical"
android:padding="20dp">
<RelativeLayout <TextView
android:id="@+id/attendance_dialog_relative_layout" android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingBottom="20dp" android:text="@string/all_subject"
android:paddingEnd="20dp" android:textIsSelectable="true"
android:paddingLeft="20dp" android:textSize="17sp" />
android:paddingRight="20dp"
android:paddingStart="20dp"
android:paddingTop="10dp"
tools:ignore="UselessParent">
<TextView <TextView
android:id="@+id/attendance_dialog_details" android:id="@+id/attendanceDialogSubject"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentLeft="true" android:layout_marginTop="3dp"
android:layout_alignParentStart="true" android:text="@string/all_no_data"
android:layout_alignParentTop="true" android:textIsSelectable="true"
android:layout_gravity="start" android:textSize="12sp" />
android:gravity="center_vertical"
android:maxLines="5"
android:minHeight="60dp"
android:minLines="2"
android:paddingTop="10dp"
android:text="@string/all_details"
android:textIsSelectable="true"
android:textSize="20sp" />
<TextView <TextView
android:id="@+id/attendance_dialog_subject" android:layout_width="wrap_content"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:layout_marginTop="10dp"
android:layout_alignParentLeft="true" android:text="@string/all_description"
android:layout_alignParentStart="true" android:textSize="17sp" />
android:layout_below="@+id/attendance_dialog_details"
android:layout_marginTop="10dp"
android:text="@string/all_subject"
android:textIsSelectable="true"
android:textSize="17sp" />
<TextView <TextView
android:id="@+id/attendance_dialog_subject_value" android:id="@+id/attendanceDialogDescription"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentLeft="true" android:layout_marginTop="3dp"
android:layout_alignParentStart="true" android:text="@string/all_no_data"
android:layout_below="@+id/attendance_dialog_subject" android:textIsSelectable="true"
android:layout_marginTop="3dp" android:textSize="12sp" />
android:text="@string/all_no_data"
android:textIsSelectable="true"
android:textSize="12sp" />
<TextView <TextView
android:id="@+id/attendance_dialog_description" android:layout_width="wrap_content"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:layout_marginTop="10dp"
android:layout_alignParentLeft="true" android:text="@string/all_date"
android:layout_alignParentStart="true" android:textSize="17sp" />
android:layout_below="@+id/attendance_dialog_subject_value"
android:layout_marginTop="10dp"
android:text="@string/all_description"
android:textSize="17sp" />
<TextView <TextView
android:id="@+id/attendance_dialog_description_value" android:id="@+id/attendanceDialogDate"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentLeft="true" android:layout_marginTop="3dp"
android:layout_alignParentStart="true" android:text="@string/all_no_data"
android:layout_below="@+id/attendance_dialog_description" android:textIsSelectable="true"
android:layout_marginTop="3dp" android:textSize="12sp" />
android:text="@string/all_no_data"
android:textIsSelectable="true"
android:textSize="12sp" />
<TextView <TextView
android:id="@+id/attendance_dialog_date" android:layout_width="wrap_content"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:layout_marginTop="10dp"
android:layout_alignParentLeft="true" android:text="@string/attendance_number"
android:layout_alignParentStart="true" android:textSize="17sp" />
android:layout_below="@+id/attendance_dialog_description_value"
android:layout_marginTop="10dp"
android:text="@string/all_date"
android:textSize="17sp" />
<TextView <TextView
android:id="@+id/attendance_dialog_date_value" android:id="@+id/attendanceDialogNumber"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentLeft="true" android:layout_marginTop="3dp"
android:layout_alignParentStart="true" android:text="@string/all_no_data"
android:layout_below="@+id/attendance_dialog_date" android:textIsSelectable="true"
android:layout_marginTop="3dp" android:textSize="12sp" />
android:text="@string/all_no_data"
android:textIsSelectable="true"
android:textSize="12sp" />
<TextView <Button
android:id="@+id/attendance_dialog_number" android:id="@+id/attendanceDialogClose"
android:layout_width="wrap_content" style="@style/Widget.AppCompat.Button.Borderless"
android:layout_height="wrap_content" android:layout_width="wrap_content"
android:layout_alignParentLeft="true" android:layout_height="wrap_content"
android:layout_alignParentStart="true" android:layout_gravity="end"
android:layout_below="@+id/attendance_dialog_date_value" android:layout_marginTop="15dp"
android:layout_marginTop="10dp" android:padding="0dp"
android:text="@string/attendance_number" android:text="@string/all_close"
android:textSize="17sp" /> android:textAllCaps="true"
android:textSize="15sp" />
<TextView
android:id="@+id/attendance_dialog_number_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/attendance_dialog_number"
android:layout_marginTop="3dp"
android:text="@string/all_no_data"
android:textIsSelectable="true"
android:textSize="12sp" />
<Button
android:id="@+id/attendance_dialog_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignTop="@+id/attendance_dialog_number_value"
android:layout_marginTop="25dp"
android:background="?attr/selectableItemBackground"
android:focusable="true"
android:text="@string/all_close"
android:textAllCaps="true"
android:textColor="?android:attr/android:textColorSecondary"
android:textSize="15sp" />
</RelativeLayout>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@ -1,30 +1,99 @@
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/attendanceContainer" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_alignParentBottom="true"> android:orientation="vertical">
<TextView <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="0dp"
android:gravity="center" android:layout_weight="1"
android:text="Attendance" /> android:orientation="vertical">
<!--<RelativeLayout <ProgressBar
android:layout_width="match_parent" android:id="@+id/attendanceProgress"
android:layout_height="match_parent"> android:layout_width="wrap_content"
<android.support.design.widget.TabLayout
android:id="@+id/attendance_fragment_tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:tabMinWidth="125dp" android:layout_gravity="center"
app:tabMode="scrollable"/> android:indeterminate="true" />
<android.support.v4.view.ViewPager <android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/attendance_fragment_viewpager" android:id="@+id/attendanceSwipe"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/attendanceRecycler"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v4.widget.SwipeRefreshLayout>
<LinearLayout
android:id="@+id/attendanceEmpty"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_below="@id/attendance_fragment_tab_layout" /> android:gravity="center"
</RelativeLayout>--> android:orientation="vertical"
</android.support.design.widget.CoordinatorLayout> android:padding="10dp"
android:visibility="gone">
<android.support.v7.widget.AppCompatImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="100dp"
android:minWidth="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>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:orientation="horizontal">
<Button
android:id="@+id/attendancePreviousButton"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawablePadding="4dp"
android:gravity="start|center"
android:text="@string/prev"
android:textAlignment="gravity" />
<TextView
android:id="@+id/attendanceNavDate"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/app_name" />
<Button
android:id="@+id/attendanceNextButton"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_weight="1"
android:drawablePadding="4dp"
android:gravity="end|center"
android:text="@string/next"
android:textAlignment="gravity" />
</LinearLayout>
</LinearLayout>

View File

@ -1,77 +1,65 @@
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout 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"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tool="http://schemas.android.com/tools" xmlns:tool="http://schemas.android.com/tools"
android:id="@+id/attendance_subItem_cardView" android:id="@+id/attendanceItemContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="7dp"
android:layout_marginEnd="5dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_marginStart="5dp"
android:foreground="?attr/selectableItemBackgroundBorderless" android:foreground="?attr/selectableItemBackgroundBorderless"
card_view:cardElevation="0dp"> android:paddingStart="12dp"
android:paddingLeft="12dp"
android:paddingTop="7dp"
android:paddingEnd="12dp"
android:paddingRight="12dp"
android:paddingBottom="7dp">
<RelativeLayout <TextView
android:layout_width="match_parent" android:id="@+id/attendanceItemNumber"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_centerVertical="true"
android:gravity="center"
android:maxLength="2"
android:text="0"
android:textSize="32sp"
tool:ignore="all" />
<TextView
android:id="@+id/attendanceItemSubject"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="7dp" android:layout_alignParentTop="true"
android:layout_marginEnd="7dp" android:layout_marginStart="10dp"
android:layout_marginLeft="7dp" android:layout_marginLeft="10dp"
android:layout_marginRight="7dp" android:layout_marginEnd="40dp"
android:layout_marginStart="7dp" android:layout_marginRight="40dp"
android:layout_marginTop="7dp"> android:layout_toEndOf="@+id/attendanceItemNumber"
android:layout_toRightOf="@+id/attendanceItemNumber"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/app_name"
android:textSize="17sp"
tool:ignore="all" />
<TextView <TextView
android:id="@+id/attendance_subItem_number" android:id="@+id/attendanceItemDescription"
android:layout_width="40dp" android:layout_width="wrap_content"
android:layout_height="40dp" android:layout_height="wrap_content"
android:layout_centerVertical="true" android:layout_alignStart="@id/attendanceItemSubject"
android:gravity="center" android:layout_alignLeft="@id/attendanceItemSubject"
android:maxLength="2" android:layout_alignBottom="@+id/attendanceItemNumber"
android:text="0" android:maxLines="1"
android:textSize="32sp" android:text="@string/app_name"
tool:ignore="all" /> android:textColor="?android:attr/android:textColorSecondary"
android:textSize="12sp" />
<TextView <ImageView
android:id="@+id/attendance_subItem_lesson" android:id="@+id/attendanceItemAlert"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true" android:layout_alignParentEnd="true"
android:layout_marginEnd="40dp" android:layout_alignParentRight="true"
android:layout_marginLeft="10dp" android:layout_marginTop="10dp"
android:layout_marginRight="40dp" app:srcCompat="@drawable/ic_all_note_24dp"
android:layout_marginStart="10dp" tool:ignore="contentDescription" />
android:layout_toEndOf="@+id/attendance_subItem_number"
android:layout_toRightOf="@+id/attendance_subItem_number"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/app_name"
android:textSize="17sp"
tool:ignore="all" />
<TextView </RelativeLayout>
android:id="@+id/attendance_subItem_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/attendance_subItem_number"
android:layout_alignLeft="@id/attendance_subItem_lesson"
android:layout_alignStart="@id/attendance_subItem_lesson"
android:maxLines="1"
android:text="@string/app_name"
android:textColor="?android:attr/android:textColorSecondary"
android:textSize="12sp" />
<ImageView
android:id="@+id/attendance_subItem_alert_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginTop="10dp"
app:srcCompat="@drawable/ic_all_note_24dp"
tool:ignore="contentDescription" />
</RelativeLayout>
</android.support.v7.widget.CardView>

View File

@ -0,0 +1,56 @@
package io.github.wulkanowy.data.repositories.remote
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.api.attendance.Attendance
import io.github.wulkanowy.data.db.entities.Semester
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.reactivex.Single
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.threeten.bp.LocalDate
import java.sql.Date
class AttendanceRemoteTest {
@MockK
private lateinit var mockApi: Api
@MockK
private lateinit var semesterMock: Semester
@Before
fun initApi() {
MockKAnnotations.init(this)
}
@Test
fun getExamsTest() {
every { mockApi.getAttendance(
LocalDate.of(2018, 9, 10),
LocalDate.of(2018, 9, 15)
) } returns Single.just(listOf(
getAttendance("2018-09-10"),
getAttendance("2018-09-17")
))
every { mockApi.diaryId } returns "1"
every { semesterMock.studentId } returns "1"
every { semesterMock.diaryId } returns "1"
val attendance = AttendanceRemote(mockApi).getAttendance(semesterMock,
LocalDate.of(2018, 9, 10),
LocalDate.of(2018, 9, 15)).blockingGet()
assertEquals(2, attendance.size)
}
private fun getAttendance(dateString: String): Attendance {
return Attendance().apply {
subject = "Fizyka"
name = "Obecność"
date = Date.valueOf(dateString)
}
}
}

View File

@ -3,41 +3,47 @@ package io.github.wulkanowy.data.repositories.remote
import io.github.wulkanowy.api.Api import io.github.wulkanowy.api.Api
import io.github.wulkanowy.api.exams.Exam import io.github.wulkanowy.api.exams.Exam
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.reactivex.Single import io.reactivex.Single
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.mockito.ArgumentMatchers.any
import org.mockito.Mock
import org.mockito.Mockito.doReturn
import org.mockito.MockitoAnnotations
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import java.sql.Date import java.sql.Date
class ExamRemoteTest { class ExamRemoteTest {
@Mock @MockK
private lateinit var mockApi: Api private lateinit var mockApi: Api
@Mock @MockK
private lateinit var semesterMock: Semester private lateinit var semesterMock: Semester
@Before @Before
fun initApi() { fun initApi() {
MockitoAnnotations.initMocks(this) MockKAnnotations.init(this)
} }
@Test @Test
fun getExamsTest() { fun getExamsTest() {
doReturn(Single.just(listOf( every { mockApi.getExams(
LocalDate.of(2018, 9, 10),
LocalDate.of(2018, 9, 15)
) } returns Single.just(listOf(
getExam("2018-09-10"), getExam("2018-09-10"),
getExam("2018-09-17") getExam("2018-09-17")
))).`when`(mockApi).getExams(any()) ))
doReturn("1").`when`(semesterMock).studentId every { mockApi.diaryId } returns "1"
doReturn("1").`when`(semesterMock).diaryId every { semesterMock.studentId } returns "1"
every { semesterMock.diaryId } returns "1"
val exams = ExamRemote(mockApi).getExams(semesterMock, LocalDate.of(2018, 9, 10)).blockingGet() val exams = ExamRemote(mockApi).getExams(semesterMock,
LocalDate.of(2018, 9, 10),
LocalDate.of(2018, 9, 15)
).blockingGet()
assertEquals(2, exams.size) assertEquals(2, exams.size)
} }

View File

@ -1,140 +0,0 @@
package io.github.wulkanowy.utils
import org.junit.Test
import org.threeten.bp.LocalDate
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
class TimeUtilsTest {
@Test fun getParsedDateTest() {
assertEquals(LocalDate.of(1970, 1, 1),
getParsedDate("1970-01-01", "yyyy-MM-dd"))
}
@Test fun getMondaysFromCurrentSchoolYearTest() {
val y201718 = getMondaysFromCurrentSchoolYear(LocalDate.of(2018, 1, 1))
assertEquals("2017-09-04", y201718.first())
assertEquals("2018-08-27", y201718.last())
val y202122 = getMondaysFromCurrentSchoolYear(LocalDate.of(2022, 1, 1))
assertEquals("2021-08-30", y202122.first())
assertEquals("2022-08-22", y202122.last())
val y202223 = getMondaysFromCurrentSchoolYear(LocalDate.of(2023, 1, 1))
assertEquals("2022-08-29", y202223.first())
assertEquals("2023-08-28", y202223.last())
}
@Test fun getCurrentSchoolYearTest() {
assertEquals(2017, getSchoolYearForDate(LocalDate.of(2018, 8, 31)))
assertEquals(2018, getSchoolYearForDate(LocalDate.of(2018, 9, 1)))
}
@Test fun getFirstWeekDayTest() {
assertEquals("2018-06-18", getFirstDayOfCurrentWeek(LocalDate.of(2018, 6, 21)))
assertEquals("2018-06-18", getFirstDayOfCurrentWeek(LocalDate.of(2018, 6, 22)))
assertEquals("2018-06-25", getFirstDayOfCurrentWeek(LocalDate.of(2018, 6, 23)))
assertEquals("2018-06-25", getFirstDayOfCurrentWeek(LocalDate.of(2018, 6, 24)))
assertEquals("2018-06-25", getFirstDayOfCurrentWeek(LocalDate.of(2018, 6, 25)))
assertEquals("2018-06-25", getFirstDayOfCurrentWeek(LocalDate.of(2018, 6, 26)))
}
@Test fun getTodayOrNextDayOrderTest() {
assertEquals(0, getTodayOrNextDayOrder(true, LocalDate.of(2018, 6, 24))) // sunday
assertEquals(6, getTodayOrNextDayOrder(false, LocalDate.of(2018, 6, 24)))
assertEquals(1, getTodayOrNextDayOrder(true, LocalDate.of(2018, 6, 25)))
assertEquals(0, getTodayOrNextDayOrder(false, LocalDate.of(2018, 6, 25)))
}
@Test fun getTodayOrNextDayTest() {
assertEquals("2018-06-26", getTodayOrNextDay(false, LocalDate.of(2018, 6, 26)))
assertEquals("2018-06-27", getTodayOrNextDay(true, LocalDate.of(2018, 6, 26)))
}
@Test fun isDateInWeekInsideTest() {
assertTrue(isDateInWeek(
LocalDate.of(2018, 5, 28),
LocalDate.of(2018, 5, 31)
))
}
@Test fun isDateInWeekExtremesTest() {
assertTrue(isDateInWeek(
LocalDate.of(2018, 5, 28),
LocalDate.of(2018, 5, 28)
))
assertTrue(isDateInWeek(
LocalDate.of(2018, 5, 28),
LocalDate.of(2018, 6, 1)
))
}
@Test fun isDateInWeekOutOfTest() {
assertFalse(isDateInWeek(
LocalDate.of(2018, 5, 28),
LocalDate.of(2018, 6, 2)
))
assertFalse(isDateInWeek(
LocalDate.of(2018, 5, 28),
LocalDate.of(2018, 5, 27)
))
}
@Test fun isHolidaysInSchoolEndTest() {
assertFalse(isHolidays(LocalDate.of(2017, 6, 23)))
assertFalse(isHolidays(LocalDate.of(2018, 6, 22)))
assertFalse(isHolidays(LocalDate.of(2019, 6, 21)))
assertFalse(isHolidays(LocalDate.of(2020, 6, 26)))
assertFalse(isHolidays(LocalDate.of(2021, 6, 25)))
assertFalse(isHolidays(LocalDate.of(2022, 6, 24)))
assertFalse(isHolidays(LocalDate.of(2023, 6, 23)))
assertFalse(isHolidays(LocalDate.of(2024, 6, 21)))
assertFalse(isHolidays(LocalDate.of(2025, 6, 27)))
}
@Test fun isHolidaysInHolidaysStartTest() {
assertTrue(isHolidays(LocalDate.of(2017, 6, 24)))
assertTrue(isHolidays(LocalDate.of(2018, 6, 23)))
assertTrue(isHolidays(LocalDate.of(2019, 6, 22)))
assertTrue(isHolidays(LocalDate.of(2020, 6, 27)))
assertTrue(isHolidays(LocalDate.of(2021, 6, 26)))
assertTrue(isHolidays(LocalDate.of(2022, 6, 25)))
assertTrue(isHolidays(LocalDate.of(2023, 6, 24)))
assertTrue(isHolidays(LocalDate.of(2024, 6, 22)))
assertTrue(isHolidays(LocalDate.of(2025, 6, 28)))
}
@Test fun isHolidaysInHolidaysEndTest() {
assertTrue(isHolidays(LocalDate.of(2017, 9, 1))) // friday
assertTrue(isHolidays(LocalDate.of(2017, 9, 2))) // saturday
assertTrue(isHolidays(LocalDate.of(2017, 9, 3))) // sunday
assertTrue(isHolidays(LocalDate.of(2018, 9, 1))) // saturday
assertTrue(isHolidays(LocalDate.of(2018, 9, 2))) // sunday
assertTrue(isHolidays(LocalDate.of(2019, 9, 1))) // sunday
assertTrue(isHolidays(LocalDate.of(2020, 8, 31))) // monday
assertTrue(isHolidays(LocalDate.of(2021, 8, 31))) // tuesday
assertTrue(isHolidays(LocalDate.of(2022, 8, 31))) // wednesday
assertTrue(isHolidays(LocalDate.of(2023, 9, 1))) // friday
assertTrue(isHolidays(LocalDate.of(2023, 9, 2))) // saturday
assertTrue(isHolidays(LocalDate.of(2023, 9, 3))) // sunday
assertTrue(isHolidays(LocalDate.of(2024, 9, 1))) // sunday
assertTrue(isHolidays(LocalDate.of(2025, 8, 31))) // sunday
}
@Test fun isHolidaysInSchoolStartTest() {
assertFalse(isHolidays(LocalDate.of(2017, 9, 4))) // monday
assertFalse(isHolidays(LocalDate.of(2018, 9, 3))) // monday
assertFalse(isHolidays(LocalDate.of(2019, 9, 2))) // monday
assertFalse(isHolidays(LocalDate.of(2020, 9, 1))) // tuesday
assertFalse(isHolidays(LocalDate.of(2021, 9, 1))) // wednesday
assertFalse(isHolidays(LocalDate.of(2022, 9, 1))) // thursday
assertFalse(isHolidays(LocalDate.of(2023, 9, 4))) // monday
assertFalse(isHolidays(LocalDate.of(2024, 9, 2))) // monday
assertFalse(isHolidays(LocalDate.of(2025, 9, 1))) // monday
}
}

View File

@ -0,0 +1,150 @@
package io.github.wulkanowy.utils.extension
import org.junit.Assert.*
import org.junit.Test
import org.threeten.bp.LocalDate
import java.util.*
class TimeExtensionTest {
@Test
fun toDate() {
assertEquals(LocalDate.of(1970, 1, 1), "1970-01-01".toDate("yyyy-MM-dd"))
}
@Test
fun toFormat() {
assertEquals("2018-10-01", LocalDate.of(2018, 10, 1).toFormat())
assertEquals("2018-10.01", LocalDate.of(2018, 10, 1).toFormat("yyyy-MM.dd"))
}
@Test
fun getWeekFirstDayAlwaysCurrent() {
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 10, 2).getWeekFirstDayAlwaysCurrent())
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 10, 5).getWeekFirstDayAlwaysCurrent())
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 10, 6).getWeekFirstDayAlwaysCurrent())
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 10, 7).getWeekFirstDayAlwaysCurrent())
assertEquals(LocalDate.of(2018, 10, 8), LocalDate.of(2018, 10, 8).getWeekFirstDayAlwaysCurrent())
}
@Test
fun getWeekFirstDayNextOnWeekEnd() {
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 10, 2).getWeekFirstDayNextOnWeekEnd())
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 10, 5).getWeekFirstDayNextOnWeekEnd())
assertEquals(LocalDate.of(2018, 10, 8), LocalDate.of(2018, 10, 6).getWeekFirstDayNextOnWeekEnd())
assertEquals(LocalDate.of(2018, 10, 8), LocalDate.of(2018, 10, 7).getWeekFirstDayNextOnWeekEnd())
assertEquals(LocalDate.of(2018, 10, 8), LocalDate.of(2018, 10, 8).getWeekFirstDayNextOnWeekEnd())
}
@Test
fun getWeekDayName() {
Locale.setDefault(Locale.forLanguageTag("PL"))
assertEquals("poniedziałek", LocalDate.of(2018, 10, 1).getWeekDayName())
Locale.setDefault(Locale.forLanguageTag("US"))
assertEquals("Monday", LocalDate.of(2018, 10, 1).getWeekDayName())
}
@Test
fun getSchoolYear() {
assertEquals(2017, LocalDate.of(2018, 8, 31).getSchoolYear())
assertEquals(2018, LocalDate.of(2018, 9, 1).getSchoolYear())
}
@Test
fun getNextSchoolDay() {
assertEquals(LocalDate.of(2018, 10, 2), LocalDate.of(2018, 10, 1).getNextWorkDay())
assertEquals(LocalDate.of(2018, 10, 3), LocalDate.of(2018, 10, 2).getNextWorkDay())
assertEquals(LocalDate.of(2018, 10, 4), LocalDate.of(2018, 10, 3).getNextWorkDay())
assertEquals(LocalDate.of(2018, 10, 5), LocalDate.of(2018, 10, 4).getNextWorkDay())
assertEquals(LocalDate.of(2018, 10, 8), LocalDate.of(2018, 10, 5).getNextWorkDay())
assertEquals(LocalDate.of(2018, 10, 8), LocalDate.of(2018, 10, 6).getNextWorkDay())
assertEquals(LocalDate.of(2018, 10, 8), LocalDate.of(2018, 10, 7).getNextWorkDay())
assertEquals(LocalDate.of(2018, 10, 9), LocalDate.of(2018, 10, 8).getNextWorkDay())
}
@Test
fun getPreviousSchoolDay() {
assertEquals(LocalDate.of(2018, 10, 9), LocalDate.of(2018, 10, 10).getPreviousWorkDay())
assertEquals(LocalDate.of(2018, 10, 8), LocalDate.of(2018, 10, 9).getPreviousWorkDay())
assertEquals(LocalDate.of(2018, 10, 5), LocalDate.of(2018, 10, 8).getPreviousWorkDay())
assertEquals(LocalDate.of(2018, 10, 5), LocalDate.of(2018, 10, 7).getPreviousWorkDay())
assertEquals(LocalDate.of(2018, 10, 5), LocalDate.of(2018, 10, 6).getPreviousWorkDay())
assertEquals(LocalDate.of(2018, 10, 4), LocalDate.of(2018, 10, 5).getPreviousWorkDay())
assertEquals(LocalDate.of(2018, 10, 3), LocalDate.of(2018, 10, 4).getPreviousWorkDay())
}
@Test
fun getNearSchoolDayPrevOnWeekEnd() {
assertEquals(LocalDate.of(2018, 9, 28), LocalDate.of(2018, 9, 28).getNearSchoolDayPrevOnWeekEnd())
assertEquals(LocalDate.of(2018, 9, 28), LocalDate.of(2018, 9, 29).getNearSchoolDayPrevOnWeekEnd())
assertEquals(LocalDate.of(2018, 9, 28), LocalDate.of(2018, 9, 30).getNearSchoolDayPrevOnWeekEnd())
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 10, 1).getNearSchoolDayPrevOnWeekEnd())
assertEquals(LocalDate.of(2018, 10, 2), LocalDate.of(2018, 10, 2).getNearSchoolDayPrevOnWeekEnd())
}
@Test
fun getNearSchoolDayNextOnWeekEnd() {
assertEquals(LocalDate.of(2018, 9, 28), LocalDate.of(2018, 9, 28).getNearSchoolDayNextOnWeekEnd())
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 9, 29).getNearSchoolDayNextOnWeekEnd())
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 9, 30).getNearSchoolDayNextOnWeekEnd())
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 10, 1).getNearSchoolDayNextOnWeekEnd())
assertEquals(LocalDate.of(2018, 10, 2), LocalDate.of(2018, 10, 2).getNearSchoolDayNextOnWeekEnd())
}
@Test
fun isHolidays_schoolEnd() {
assertFalse(LocalDate.of(2017, 6, 23).isHolidays())
assertFalse(LocalDate.of(2018, 6, 22).isHolidays())
assertFalse(LocalDate.of(2019, 6, 21).isHolidays())
assertFalse(LocalDate.of(2020, 6, 26).isHolidays())
assertFalse(LocalDate.of(2021, 6, 25).isHolidays())
assertFalse(LocalDate.of(2022, 6, 24).isHolidays())
assertFalse(LocalDate.of(2023, 6, 23).isHolidays())
assertFalse(LocalDate.of(2024, 6, 21).isHolidays())
assertFalse(LocalDate.of(2025, 6, 27).isHolidays())
}
@Test
fun isHolidays_holidaysStart() {
assertTrue(LocalDate.of(2017, 6, 24).isHolidays())
assertTrue(LocalDate.of(2018, 6, 23).isHolidays())
assertTrue(LocalDate.of(2019, 6, 22).isHolidays())
assertTrue(LocalDate.of(2020, 6, 27).isHolidays())
assertTrue(LocalDate.of(2021, 6, 26).isHolidays())
assertTrue(LocalDate.of(2022, 6, 25).isHolidays())
assertTrue(LocalDate.of(2023, 6, 24).isHolidays())
assertTrue(LocalDate.of(2024, 6, 22).isHolidays())
assertTrue(LocalDate.of(2025, 6, 28).isHolidays())
}
@Test
fun isHolidays_holidaysEnd() {
assertTrue(LocalDate.of(2017, 9, 1).isHolidays()) // friday
assertTrue(LocalDate.of(2017, 9, 2).isHolidays()) // saturday
assertTrue(LocalDate.of(2017, 9, 3).isHolidays()) // sunday
assertTrue(LocalDate.of(2018, 9, 1).isHolidays()) // saturday
assertTrue(LocalDate.of(2018, 9, 2).isHolidays()) // sunday
assertTrue(LocalDate.of(2019, 9, 1).isHolidays()) // sunday
assertTrue(LocalDate.of(2020, 8, 31).isHolidays()) // monday
assertTrue(LocalDate.of(2021, 8, 31).isHolidays()) // tuesday
assertTrue(LocalDate.of(2022, 8, 31).isHolidays()) // wednesday
assertTrue(LocalDate.of(2023, 9, 1).isHolidays()) // friday
assertTrue(LocalDate.of(2023, 9, 2).isHolidays()) // saturday
assertTrue(LocalDate.of(2023, 9, 3).isHolidays()) // sunday
assertTrue(LocalDate.of(2024, 9, 1).isHolidays()) // sunday
assertTrue(LocalDate.of(2025, 8, 31).isHolidays()) // sunday
}
@Test
fun isHolidays_schoolStart() {
assertFalse(LocalDate.of(2017, 9, 4).isHolidays()) // monday
assertFalse(LocalDate.of(2018, 9, 3).isHolidays()) // monday
assertFalse(LocalDate.of(2019, 9, 2).isHolidays()) // monday
assertFalse(LocalDate.of(2020, 9, 1).isHolidays()) // tuesday
assertFalse(LocalDate.of(2021, 9, 1).isHolidays()) // wednesday
assertFalse(LocalDate.of(2022, 9, 1).isHolidays()) // thursday
assertFalse(LocalDate.of(2023, 9, 4).isHolidays()) // monday
assertFalse(LocalDate.of(2024, 9, 2).isHolidays()) // monday
assertFalse(LocalDate.of(2025, 9, 1).isHolidays()) // monday
}
}

View File

@ -9,7 +9,7 @@ buildscript {
} }
dependencies { dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.android.tools.build:gradle:3.1.4' classpath 'com.android.tools.build:gradle:3.2.0'
classpath "io.fabric.tools:gradle:1.25.4" classpath "io.fabric.tools:gradle:1.25.4"
classpath "com.google.gms:oss-licenses:0.9.2" classpath "com.google.gms:oss-licenses:0.9.2"
classpath "com.github.triplet.gradle:play-publisher:1.2.2" classpath "com.github.triplet.gradle:play-publisher:1.2.2"

View File

@ -1,6 +1,6 @@
#Tue Mar 27 02:10:44 CEST 2018 #Sat Sep 29 23:00:14 CEST 2018
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip