1
0
mirror of https://github.com/wulkanowy/wulkanowy.git synced 2024-09-19 22:29:09 -05:00

Modules improvements (#164)

This commit is contained in:
Rafał Borcz 2018-10-14 22:16:58 +02:00 committed by Mikołaj Pich
parent bb69d1b643
commit 27f6fc7e04
90 changed files with 881 additions and 1069 deletions

View File

@ -7,7 +7,7 @@ references:
container_config: &container_config
docker:
- image: circleci/android:api-27-alpha
- image: circleci/android:api-28-alpha
working_directory: *workspace_root
environment:
environment:

View File

@ -73,6 +73,7 @@ ext.supportVersion = "28.0.0"
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation('com.github.wulkanowy:api:a5667f8000') { exclude module: "threetenbp" }
implementation "com.android.support:support-v4:$supportVersion"
implementation "com.android.support:appcompat-v7:$supportVersion"
implementation "com.android.support:design:$supportVersion"
@ -116,6 +117,7 @@ dependencies {
testImplementation "junit:junit:4.12"
testImplementation "io.mockk:mockk:1.8.8"
testImplementation "org.mockito:mockito-inline:2.21.0"
testImplementation 'org.threeten:threetenbp:1.3.7'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation "org.mockito:mockito-android:2.21.0"

View File

@ -34,9 +34,9 @@ class AttendanceLocalTest {
@Test
fun saveAndReadTest() {
attendanceLocal.saveAttendance(listOf(
Attendance(0, "1", "2", LocalDate.of(2018, 9, 10), 0, "", ""),
Attendance(0, "1", "2", LocalDate.of(2018, 9, 14), 0, "", ""),
Attendance(0, "1", "2", LocalDate.of(2018, 9, 17), 0, "", "")
Attendance("1", "2", LocalDate.of(2018, 9, 10), 0, "", ""),
Attendance("1", "2", LocalDate.of(2018, 9, 14), 0, "", ""),
Attendance("1", "2", LocalDate.of(2018, 9, 17), 0, "", "")
))
val attendance = attendanceLocal

View File

@ -34,9 +34,9 @@ class ExamLocalTest {
@Test
fun saveAndReadTest() {
examLocal.saveExams(listOf(
Exam(0, "1", "2", LocalDate.of(2018, 9, 10), LocalDate.now(), "", "", "", "", "", ""),
Exam(0, "1", "2", LocalDate.of(2018, 9, 14), LocalDate.now(), "", "", "", "", "", ""),
Exam(0, "1", "2", LocalDate.of(2018, 9, 17), LocalDate.now(), "", "", "", "", "", "")
Exam("1", "2", LocalDate.of(2018, 9, 10), LocalDate.now(), "", "", "", "", "", ""),
Exam("1", "2", LocalDate.of(2018, 9, 14), LocalDate.now(), "", "", "", "", "", ""),
Exam("1", "2", LocalDate.of(2018, 9, 17), LocalDate.now(), "", "", "", "", "", "")
))
val exams = examLocal

View File

@ -11,6 +11,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDateTime
import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
@ -33,14 +34,17 @@ class TimetableLocalTest {
@Test
fun saveAndReadTest() {
timetableDb.saveLessons(listOf(
Timetable(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 10)),
Timetable(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 14)),
Timetable(studentId = "1", diaryId = "2", date = LocalDate.of(2018, 9, 17)) // in next week
timetableDb.saveTimetable(listOf(
Timetable("1", "2", 1, LocalDateTime.now(), LocalDateTime.now(),
LocalDate.of(2018, 9, 10), "", "", "", "", ""),
Timetable("1", "2", 1, LocalDateTime.now(), LocalDateTime.now(),
LocalDate.of(2018, 9, 14), "", "", "", "", ""),
Timetable("1", "2", 1, LocalDateTime.now(), LocalDateTime.now(),
LocalDate.of(2018, 9, 17), "", "", "", "", "")
))
val exams = timetableDb.getLessons(
Semester(studentId = "1", diaryId = "2", semesterId = "3", diaryName = "", semesterName = 1),
val exams = timetableDb.getTimetable(
Semester(0, "1", "2", "3", "", 1),
LocalDate.of(2018, 9, 10),
LocalDate.of(2018, 9, 14)
).blockingGet()

View File

@ -17,32 +17,32 @@ class ScramblerTest {
@Test
fun encryptDecryptTest() {
assertEquals("TEST", Scrambler.decrypt(Scrambler.encrypt("TEST",
assertEquals("TEST", decrypt(encrypt("TEST",
InstrumentationRegistry.getTargetContext())))
}
@Test
fun emptyTextEncryptTest() {
assertFailsWith<ScramblerException> {
Scrambler.decrypt("")
decrypt("")
}
assertFailsWith<ScramblerException> {
Scrambler.encrypt("", InstrumentationRegistry.getTargetContext())
encrypt("", InstrumentationRegistry.getTargetContext())
}
}
@Test
@SdkSuppress(minSdkVersion = 18)
fun emptyKeyStoreTest() {
val text = Scrambler.encrypt("test", InstrumentationRegistry.getTargetContext())
val text = encrypt("test", InstrumentationRegistry.getTargetContext())
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
keyStore.deleteEntry("USER_PASSWORD")
assertFailsWith<ScramblerException> {
Scrambler.decrypt(text)
decrypt(text)
}
}
}

View File

@ -1,6 +1,7 @@
package io.github.wulkanowy.data.db.dao
import android.arch.persistence.room.Dao
import android.arch.persistence.room.Delete
import android.arch.persistence.room.Insert
import android.arch.persistence.room.OnConflictStrategy.REPLACE
import android.arch.persistence.room.Query
@ -13,6 +14,9 @@ interface GradeSummaryDao {
@Insert(onConflict = REPLACE)
fun insertAll(gradesSummary: List<GradeSummary>)
@Delete
fun deleteAll(gradesSummary: List<GradeSummary>)
@Query("SELECT * FROM grades_summary WHERE student_id = :studentId AND semester_id = :semesterId")
fun getGradesSummary(semesterId: String, studentId: String): Maybe<List<GradeSummary>>
}

View File

@ -2,7 +2,6 @@ package io.github.wulkanowy.data.db.dao
import android.arch.persistence.room.Dao
import android.arch.persistence.room.Insert
import android.arch.persistence.room.OnConflictStrategy.REPLACE
import android.arch.persistence.room.Query
import io.github.wulkanowy.data.db.entities.Semester
import io.reactivex.Single
@ -10,7 +9,7 @@ import io.reactivex.Single
@Dao
interface SemesterDao {
@Insert(onConflict = REPLACE)
@Insert
fun insertAll(semester: List<Semester>)
@Query("SELECT * FROM Semesters WHERE student_id = :studentId")

View File

@ -2,7 +2,6 @@ package io.github.wulkanowy.data.db.dao
import android.arch.persistence.room.Dao
import android.arch.persistence.room.Insert
import android.arch.persistence.room.OnConflictStrategy.REPLACE
import android.arch.persistence.room.Query
import io.github.wulkanowy.data.db.entities.Student
import io.reactivex.Maybe
@ -10,7 +9,7 @@ import io.reactivex.Maybe
@Dao
interface StudentDao {
@Insert(onConflict = REPLACE)
@Insert
fun insert(student: Student): Long
@Query("SELECT * FROM Students WHERE id = :id")

View File

@ -9,9 +9,6 @@ import java.io.Serializable
@Entity(tableName = "Attendance")
data class Attendance(
@PrimaryKey(autoGenerate = true)
var id: Long = 0,
@ColumnInfo(name = "student_id")
var studentId: String,
@ -37,4 +34,8 @@ data class Attendance(
var excused: Boolean = false,
var deleted: Boolean = false
) : Serializable
) : Serializable {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}

View File

@ -9,9 +9,6 @@ import java.io.Serializable
@Entity(tableName = "Exams")
data class Exam(
@PrimaryKey(autoGenerate = true)
var id: Long = 0,
@ColumnInfo(name = "student_id")
var studentId: String,
@ -35,4 +32,8 @@ data class Exam(
@ColumnInfo(name = "teacher_symbol")
var teacherSymbol: String
) : Serializable
) : Serializable {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}

View File

@ -2,16 +2,11 @@ package io.github.wulkanowy.data.db.entities
import android.arch.persistence.room.ColumnInfo
import android.arch.persistence.room.Entity
import android.arch.persistence.room.Index
import android.arch.persistence.room.PrimaryKey
@Entity(tableName = "Grades_Summary",
indices = [Index(value = ["semester_id", "student_id", "subject"], unique = true)])
@Entity(tableName = "Grades_Summary")
data class GradeSummary(
@PrimaryKey(autoGenerate = true)
var id: Long = 0,
@ColumnInfo(name = "semester_id")
var semesterId: String,
@ -23,4 +18,8 @@ data class GradeSummary(
var predictedGrade: String,
var finalGrade: String
)
) {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}

View File

@ -2,11 +2,9 @@ package io.github.wulkanowy.data.db.entities
import android.arch.persistence.room.ColumnInfo
import android.arch.persistence.room.Entity
import android.arch.persistence.room.Index
import android.arch.persistence.room.PrimaryKey
@Entity(tableName = "Semesters",
indices = [Index(value = ["semester_id", "diary_id", "student_id"], unique = true)])
@Entity(tableName = "Semesters")
data class Semester(
@PrimaryKey(autoGenerate = true)

View File

@ -2,11 +2,9 @@ package io.github.wulkanowy.data.db.entities
import android.arch.persistence.room.ColumnInfo
import android.arch.persistence.room.Entity
import android.arch.persistence.room.Index
import android.arch.persistence.room.PrimaryKey
@Entity(tableName = "Students",
indices = [Index(value = ["student_id", "student_name"], unique = true)])
@Entity(tableName = "Students")
data class Student(
@PrimaryKey(autoGenerate = true)

View File

@ -10,14 +10,11 @@ import java.io.Serializable
@Entity(tableName = "Timetable")
data class Timetable(
@PrimaryKey(autoGenerate = true)
var id: Long = 0,
@ColumnInfo(name = "student_id")
var studentId: String = "",
var studentId: String,
@ColumnInfo(name = "diary_id")
var diaryId: String = "",
var diaryId: String,
val number: Int = 0,
@ -27,17 +24,21 @@ data class Timetable(
val date: LocalDate,
val subject: String = "",
val subject: String,
val group: String = "",
val group: String,
val room: String = "",
val room: String,
val teacher: String = "",
val teacher: String,
val info: String = "",
val info: String,
val changes: Boolean = false,
val canceled: Boolean = false
) : Serializable
) : Serializable {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}

View File

@ -6,11 +6,10 @@ import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.repositories.local.AttendanceLocal
import io.github.wulkanowy.data.repositories.remote.AttendanceRemote
import io.github.wulkanowy.utils.weekFirstDayAlwaysCurrent
import io.github.wulkanowy.utils.friday
import io.github.wulkanowy.utils.monday
import io.reactivex.Single
import org.threeten.bp.DayOfWeek
import org.threeten.bp.LocalDate
import org.threeten.bp.temporal.TemporalAdjusters
import java.net.UnknownHostException
import javax.inject.Inject
import javax.inject.Singleton
@ -22,24 +21,25 @@ class AttendanceRepository @Inject constructor(
private val remote: AttendanceRemote
) {
fun getAttendance(semester: Semester, startDate: LocalDate, endDate: LocalDate, forceRefresh: Boolean = false): Single<List<Attendance>> {
val start = startDate.weekFirstDayAlwaysCurrent
val end = endDate.with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY))
return local.getAttendance(semester, start, end).filter { !forceRefresh }
fun getAttendance(semester: Semester, startDate: LocalDate, endDate: LocalDate, forceRefresh: Boolean)
: Single<List<Attendance>> {
return Single.fromCallable { startDate.monday to endDate.friday }
.flatMap { dates ->
local.getAttendance(semester, dates.first, dates.second).filter { !forceRefresh }
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings).flatMap {
if (it) remote.getAttendance(semester, start, end)
if (it) remote.getAttendance(semester, dates.first, dates.second)
else Single.error(UnknownHostException())
}.flatMap { newLessons ->
local.getAttendance(semester, start, end).toSingle(emptyList()).map { grades ->
local.deleteAttendance(grades - newLessons)
local.saveAttendance(newLessons - grades)
newLessons
}.flatMap { newAttendance ->
local.getAttendance(semester, dates.first, dates.second)
.toSingle(emptyList())
.doOnSuccess { oldAttendance ->
local.deleteAttendance(oldAttendance - newAttendance)
local.saveAttendance(newAttendance - oldAttendance)
}
}).map { list ->
list.asSequence().filter {
it.date in startDate..endDate
}.toList()
}.flatMap {
local.getAttendance(semester, dates.first, dates.second)
.toSingle(emptyList())
}).map { list -> list.filter { it.date in startDate..endDate } }
}
}
}

View File

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

View File

@ -24,7 +24,12 @@ class GradeSummaryRepository @Inject constructor(
.flatMap {
if (it) remote.getGradeSummary(semester)
else Single.error(UnknownHostException())
}.flatMap { newGradesSummary ->
local.getGradesSummary(semester).toSingle(emptyList())
.doOnSuccess { oldGradesSummary ->
local.deleteGradesSummary(oldGradesSummary - newGradesSummary)
local.saveGradesSummary(newGradesSummary - oldGradesSummary)
}
).doOnSuccess { local.saveGradesSummary(it) }
}.flatMap { local.getGradesSummary(semester).toSingle(emptyList()) })
}
}

View File

@ -6,11 +6,10 @@ import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.repositories.local.TimetableLocal
import io.github.wulkanowy.data.repositories.remote.TimetableRemote
import io.github.wulkanowy.utils.weekFirstDayAlwaysCurrent
import io.github.wulkanowy.utils.friday
import io.github.wulkanowy.utils.monday
import io.reactivex.Single
import org.threeten.bp.DayOfWeek
import org.threeten.bp.LocalDate
import org.threeten.bp.temporal.TemporalAdjusters
import java.net.UnknownHostException
import javax.inject.Inject
import javax.inject.Singleton
@ -22,24 +21,26 @@ class TimetableRepository @Inject constructor(
private val remote: TimetableRemote
) {
fun getTimetable(semester: Semester, startDate: LocalDate, endDate: LocalDate, forceRefresh: Boolean = false): Single<List<Timetable>> {
val start = startDate.weekFirstDayAlwaysCurrent
val end = endDate.with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY))
return local.getLessons(semester, start, end).filter { !forceRefresh }
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings).flatMap {
if (it) remote.getLessons(semester, start, end)
fun getTimetable(semester: Semester, startDate: LocalDate, endDate: LocalDate, forceRefresh: Boolean = false)
: Single<List<Timetable>> {
return Single.fromCallable { startDate.monday to endDate.friday }
.flatMap { dates ->
local.getTimetable(semester, dates.first, dates.second).filter { !forceRefresh }
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap {
if (it) remote.getTimetable(semester, dates.first, dates.second)
else Single.error(UnknownHostException())
}.flatMap { newLessons ->
local.getLessons(semester, start, end).toSingle(emptyList()).map { lessons ->
local.deleteLessons(lessons - newLessons)
local.saveLessons(newLessons - lessons)
newLessons
}.flatMap { newTimetable ->
local.getTimetable(semester, dates.first, dates.second)
.toSingle(emptyList())
.doOnSuccess { oldTimetable ->
local.deleteTimetable(oldTimetable - newTimetable)
local.saveTimetable(newTimetable - oldTimetable)
}
}).map { list ->
list.asSequence().filter {
it.date in startDate..endDate
}.toList()
}.flatMap {
local.getTimetable(semester, dates.first, dates.second)
.toSingle(emptyList())
}).map { list -> list.filter { it.date in startDate..endDate } }
}
}
}

View File

@ -18,4 +18,8 @@ class GradeSummaryLocal @Inject constructor(private val gradeSummaryDb: GradeSum
fun saveGradesSummary(gradesSummary: List<GradeSummary>) {
gradeSummaryDb.insertAll(gradesSummary)
}
fun deleteGradesSummary(gradesSummary: List<GradeSummary>) {
gradeSummaryDb.deleteAll(gradesSummary)
}
}

View File

@ -6,8 +6,8 @@ import io.github.wulkanowy.data.db.dao.SemesterDao
import io.github.wulkanowy.data.db.dao.StudentDao
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.utils.security.Scrambler.decrypt
import io.github.wulkanowy.utils.security.Scrambler.encrypt
import io.github.wulkanowy.utils.security.decrypt
import io.github.wulkanowy.utils.security.encrypt
import io.reactivex.Completable
import io.reactivex.Maybe
import io.reactivex.Single

View File

@ -9,16 +9,16 @@ import javax.inject.Inject
class TimetableLocal @Inject constructor(private val timetableDb: TimetableDao) {
fun getLessons(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe<List<Timetable>> {
fun getTimetable(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe<List<Timetable>> {
return timetableDb.getTimetable(semester.diaryId, semester.studentId, startDate, endDate)
.filter { !it.isEmpty() }
}
fun saveLessons(lessons: List<Timetable>) {
timetableDb.insertAll(lessons)
fun saveTimetable(timetables: List<Timetable>) {
timetableDb.insertAll(timetables)
}
fun deleteLessons(exams: List<Timetable>) {
timetableDb.deleteAll(exams)
fun deleteTimetable(timetables: List<Timetable>) {
timetableDb.deleteAll(timetables)
}
}

View File

@ -26,7 +26,7 @@ class GradeRemote @Inject constructor(private val api: Api) {
subject = it.subject,
entry = it.entry,
value = it.value,
modifier = it.modifier.toDouble(),
modifier = it.modifier,
comment = it.comment,
color = it.color,
gradeSymbol = it.symbol,

View File

@ -11,7 +11,7 @@ import javax.inject.Inject
class TimetableRemote @Inject constructor(private val api: Api) {
fun getLessons(semester: Semester, startDate: LocalDate, endDate: LocalDate): Single<List<Timetable>> {
fun getTimetable(semester: Semester, startDate: LocalDate, endDate: LocalDate): Single<List<Timetable>> {
return Single.just(api.run {
if (diaryId != semester.diaryId) {
diaryId = semester.diaryId

View File

@ -1,10 +1,16 @@
package io.github.wulkanowy.ui.base
import android.support.design.widget.Snackbar
import android.support.design.widget.Snackbar.LENGTH_LONG
import android.view.View
import dagger.android.support.DaggerFragment
abstract class BaseFragment : DaggerFragment(), BaseView {
protected var messageContainer: View? = null
override fun showMessage(text: String) {
(activity as BaseActivity?)?.showMessage(text)
if (messageContainer == null) (activity as? BaseActivity)?.showMessage(text)
else messageContainer?.also { Snackbar.make(it, text, LENGTH_LONG).show() }
}
}

View File

@ -9,15 +9,12 @@ open class BasePresenter<T : BaseView>(private val errorHandler: ErrorHandler) {
var view: T? = null
val isViewAttached: Boolean
get() = view != null
open fun attachView(view: T) {
open fun onAttachView(view: T) {
this.view = view
errorHandler.showErrorMessage = { view.showMessage(it) }
}
open fun detachView() {
open fun onDetachView() {
view = null
disposable.clear()
errorHandler.clear()

View File

@ -27,8 +27,9 @@ class LoginActivity : BaseActivity(), LoginView, LoginSwitchListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
presenter.attachView(this)
messageContainer = loginContainer
presenter.onAttachView(this)
}
override fun onBackPressed() {
@ -65,7 +66,7 @@ class LoginActivity : BaseActivity(), LoginView, LoginSwitchListener {
override fun currentViewPosition() = loginViewpager.currentItem
public override fun onDestroy() {
presenter.detachView()
presenter.onDetachView()
super.onDestroy()
}
}

View File

@ -7,8 +7,8 @@ import javax.inject.Inject
class LoginPresenter @Inject constructor(errorHandler: ErrorHandler)
: BasePresenter<LoginView>(errorHandler) {
override fun attachView(view: LoginView) {
super.attachView(view)
override fun onAttachView(view: LoginView) {
super.onAttachView(view)
view.run {
initAdapter()
hideActionBar()

View File

@ -31,7 +31,7 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
presenter.attachView(this)
presenter.onAttachView(this)
}
override fun initInputs() {
@ -139,6 +139,6 @@ class LoginFormFragment : BaseFragment(), LoginFormView {
override fun onDestroyView() {
super.onDestroyView()
presenter.detachView()
presenter.onDetachView()
}
}

View File

@ -14,8 +14,8 @@ class LoginFormPresenter @Inject constructor(
private var wasEmpty = false
override fun attachView(view: LoginFormView) {
super.attachView(view)
override fun onAttachView(view: LoginFormView) {
super.onAttachView(view)
view.initInputs()
}

View File

@ -35,7 +35,7 @@ class LoginOptionsFragment : BaseFragment(), LoginOptionsView {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
presenter.attachView(this)
presenter.onAttachView(this)
}
override fun initRecycler() {
@ -80,6 +80,6 @@ class LoginOptionsFragment : BaseFragment(), LoginOptionsView {
override fun onDestroyView() {
super.onDestroyView()
presenter.detachView()
presenter.onDetachView()
}
}

View File

@ -13,8 +13,8 @@ class LoginOptionsPresenter @Inject constructor(
private val schedulers: SchedulersManager)
: BasePresenter<LoginOptionsView>(errorHandler) {
override fun attachView(view: LoginOptionsView) {
super.attachView(view)
override fun onAttachView(view: LoginOptionsView) {
super.onAttachView(view)
view.initRecycler()
}

View File

@ -39,7 +39,7 @@ class MainActivity : BaseActivity(), MainView {
setSupportActionBar(mainToolbar)
messageContainer = mainFragmentContainer
presenter.attachView(this)
presenter.onAttachView(this)
navController.initialize(DEFAULT_TAB, savedInstanceState)
}
@ -90,10 +90,6 @@ class MainActivity : BaseActivity(), MainView {
supportActionBar?.title = title
}
override fun expandActionBar(show: Boolean) {
mainAppBarContainer.setExpanded(show, true)
}
override fun viewTitle(index: Int): String {
return getString(listOf(R.string.grade_title,
R.string.attendance_title,
@ -119,6 +115,6 @@ class MainActivity : BaseActivity(), MainView {
override fun onDestroy() {
super.onDestroy()
presenter.detachView()
presenter.onDetachView()
}
}

View File

@ -7,8 +7,8 @@ import javax.inject.Inject
class MainPresenter @Inject constructor(errorHandler: ErrorHandler)
: BasePresenter<MainView>(errorHandler) {
override fun attachView(view: MainView) {
super.attachView(view)
override fun onAttachView(view: MainView) {
super.onAttachView(view)
view.initView()
}
@ -22,7 +22,6 @@ class MainPresenter @Inject constructor(errorHandler: ErrorHandler)
fun onTabSelected(index: Int, wasSelected: Boolean): Boolean {
return view?.run {
expandActionBar(true)
if (wasSelected) {
notifyMenuViewReselected()
false

View File

@ -10,8 +10,6 @@ interface MainView : BaseView {
fun setViewTitle(title: String)
fun expandActionBar(show: Boolean)
fun viewTitle(index: Int): String
fun currentMenuIndex(): Int

View File

@ -26,14 +26,13 @@ class AttendanceDialog : DialogFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(DialogFragment.STYLE_NORMAL, R.style.DialogFragmentTheme)
setStyle(STYLE_NO_TITLE, 0)
arguments?.run {
attendance = getSerializable(ARGUMENT_KEY) as Attendance
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
dialog.setTitle(getString(R.string.all_details))
return inflater.inflate(R.layout.dialog_attendance, container, false)
}

View File

@ -10,11 +10,12 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.main.MainView
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_attendance.*
import javax.inject.Inject
class AttendanceFragment : BaseFragment(), AttendanceView {
class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MenuFragmentView {
@Inject
lateinit var presenter: AttendancePresenter
@ -24,6 +25,7 @@ class AttendanceFragment : BaseFragment(), AttendanceView {
companion object {
private const val SAVED_DATE_KEY = "CURRENT_DATE"
fun newInstance() = AttendanceFragment()
}
@ -33,41 +35,42 @@ class AttendanceFragment : BaseFragment(), AttendanceView {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
presenter.run {
attachView(this@AttendanceFragment)
loadData(date = savedInstanceState?.getLong(SAVED_DATE_KEY))
}
messageContainer = attendanceRecycler
presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY))
}
override fun initView() {
attendanceAdapter.run {
isAutoCollapseOnExpand = true
isAutoScrollOnExpand = true
setOnItemClickListener { presenter.onAttendanceItemSelected(getItem(it))}
attendanceAdapter.apply {
setOnItemClickListener { presenter.onAttendanceItemSelected(getItem(it)) }
}
attendanceRecycler.run {
layoutManager = SmoothScrollLinearLayoutManager(context)
adapter = attendanceAdapter
}
attendanceSwipe.setOnRefreshListener { presenter.loadData(date = null, forceRefresh = true) }
attendancePreviousButton.setOnClickListener { presenter.loadAttendanceForPreviousDay() }
attendanceNextButton.setOnClickListener { presenter.loadAttendanceForNextDay() }
attendanceSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
attendancePreviousButton.setOnClickListener { presenter.onPreviousDay() }
attendanceNextButton.setOnClickListener { presenter.onNextDay() }
}
override fun updateData(data: List<AttendanceItem>) {
attendanceAdapter.updateDataSet(data, true)
}
override fun clearData() {
attendanceAdapter.clear()
}
override fun updateNavigationDay(date: String) {
attendanceNavDate.text = date
}
override fun clearData() {
attendanceAdapter.clear()
}
override fun isViewEmpty() = attendanceAdapter.isEmpty
override fun onFragmentReselected() {
presenter.onViewReselected()
}
override fun showEmpty(show: Boolean) {
attendanceEmpty.visibility = if (show) View.VISIBLE else View.GONE
}
@ -80,8 +83,8 @@ class AttendanceFragment : BaseFragment(), AttendanceView {
attendanceRecycler.visibility = if (show) View.VISIBLE else View.GONE
}
override fun showRefresh(show: Boolean) {
attendanceSwipe.isRefreshing = show
override fun hideRefresh() {
attendanceSwipe.isRefreshing = false
}
override fun showPreButton(show: Boolean) {
@ -102,8 +105,8 @@ class AttendanceFragment : BaseFragment(), AttendanceView {
}
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()
presenter.detachView()
}
}

View File

@ -1,6 +1,8 @@
package io.github.wulkanowy.ui.main.attendance
import android.view.View
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible
@ -10,9 +12,7 @@ import io.github.wulkanowy.data.db.entities.Attendance
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_attendance.*
class AttendanceItem : AbstractFlexibleItem<AttendanceItem.ViewHolder>() {
lateinit var attendance: Attendance
class AttendanceItem(val attendance: Attendance) : AbstractFlexibleItem<AttendanceItem.ViewHolder>() {
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
return ViewHolder(view, adapter)
@ -20,6 +20,16 @@ class AttendanceItem : AbstractFlexibleItem<AttendanceItem.ViewHolder>() {
override fun getLayoutRes(): Int = R.layout.item_attendance
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder,
position: Int, payloads: MutableList<Any>?) {
holder.apply {
attendanceItemNumber.text = attendance.number.toString()
attendanceItemSubject.text = attendance.subject
attendanceItemDescription.text = attendance.name
attendanceItemAlert.visibility = attendance.run { if (absence && !excused) VISIBLE else INVISIBLE }
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
@ -34,22 +44,10 @@ class AttendanceItem : AbstractFlexibleItem<AttendanceItem.ViewHolder>() {
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

@ -2,14 +2,15 @@ package io.github.wulkanowy.ui.main.attendance
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.repositories.AttendanceRepository
import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.schedulers.SchedulersManager
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDate.now
import org.threeten.bp.LocalDate.ofEpochDay
import java.util.concurrent.TimeUnit.MILLISECONDS
import javax.inject.Inject
class AttendancePresenter @Inject constructor(
@ -19,79 +20,79 @@ class AttendancePresenter @Inject constructor(
private val sessionRepository: SessionRepository
) : BasePresenter<AttendanceView>(errorHandler) {
var currentDate: LocalDate = LocalDate.now().nearSchoolDayPrevOnWeekEnd
lateinit var currentDate: LocalDate
private set
override fun attachView(view: AttendanceView) {
super.attachView(view)
fun onAttachView(view: AttendanceView, date: Long?) {
super.onAttachView(view)
view.initView()
loadData(ofEpochDay(date ?: now().previousOrSameSchoolDay.toEpochDay()))
reloadView()
}
fun loadAttendanceForPreviousDay() = loadData(currentDate.previousWorkDay.toEpochDay())
fun loadAttendanceForNextDay() = loadData(currentDate.nextWorkDay.toEpochDay())
fun loadData(date: Long?, forceRefresh: Boolean = false) {
this.currentDate = LocalDate.ofEpochDay(date
?: currentDate.nearSchoolDayPrevOnWeekEnd.toEpochDay())
if (currentDate.isHolidays) return
disposable.clear()
disposable.add(sessionRepository.getSemesters()
.map { selectSemester(it, -1) }
.flatMap { attendanceRepository.getAttendance(it, currentDate, currentDate, forceRefresh) }
.map { createAttendanceItems(it) }
.subscribeOn(schedulers.backgroundThread())
.observeOn(schedulers.mainThread())
.doOnSubscribe {
view?.run {
showRefresh(forceRefresh)
showProgress(!forceRefresh)
if (!forceRefresh) {
showEmpty(false)
clearData()
}
showPreButton(!currentDate.minusDays(1).isHolidays)
showNextButton(!currentDate.plusDays(1).isHolidays)
updateNavigationDay(currentDate.toFormattedString("EEEE \n dd.MM.YYYY").capitalize())
}
}
.doFinally {
view?.run {
showRefresh(false)
showProgress(false)
}
}
.subscribe({
view?.run {
showEmpty(it.isEmpty())
showContent(it.isNotEmpty())
updateData(it)
}
}) {
view?.run { showEmpty(isViewEmpty()) }
errorHandler.proceed(it)
})
fun onPreviousDay() {
loadData(currentDate.previousSchoolDay)
reloadView()
}
private fun createAttendanceItems(items: List<Attendance>): List<AttendanceItem> {
return items.map {
AttendanceItem().apply { attendance = it }
fun onNextDay() {
loadData(currentDate.nextSchoolDay)
reloadView()
}
fun onSwipeRefresh() {
loadData(currentDate, true)
}
fun onViewReselected() {
loadData(now().previousOrSameSchoolDay)
reloadView()
}
fun onAttendanceItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is AttendanceItem) view?.showAttendanceDialog(item.attendance)
}
private fun selectSemester(semesters: List<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
private fun loadData(date: LocalDate, forceRefresh: Boolean = false) {
currentDate = date
disposable.apply {
clear()
add(sessionRepository.getSemesters()
.delay(200, MILLISECONDS)
.map { it.single { semester -> semester.current } }
.flatMap { attendanceRepository.getAttendance(it, date, date, forceRefresh) }
.map { items -> items.map { AttendanceItem(it) } }
.subscribeOn(schedulers.backgroundThread())
.observeOn(schedulers.mainThread())
.doFinally {
view?.run {
hideRefresh()
showProgress(false)
}
}
.subscribe({
view?.apply {
updateData(it)
showEmpty(it.isEmpty())
showContent(it.isNotEmpty())
}
}) {
view?.run { showEmpty(isViewEmpty()) }
errorHandler.proceed(it)
}
)
}
}
private fun reloadView() {
view?.apply {
showProgress(true)
showContent(false)
showEmpty(false)
clearData()
showNextButton(!currentDate.plusDays(1).isHolidays)
showPreButton(!currentDate.minusDays(1).isHolidays)
updateNavigationDay(currentDate.toFormattedString("EEEE \n dd.MM.YYYY").capitalize())
}
}
}

View File

@ -9,20 +9,20 @@ interface AttendanceView : BaseView {
fun updateData(data: List<AttendanceItem>)
fun clearData()
fun updateNavigationDay(date: String)
fun clearData()
fun isViewEmpty(): Boolean
fun hideRefresh()
fun showEmpty(show: Boolean)
fun showProgress(show: Boolean)
fun showContent(show: Boolean)
fun showRefresh(show: Boolean)
fun showPreButton(show: Boolean)
fun showNextButton(show: Boolean)

View File

@ -26,14 +26,13 @@ class ExamDialog : DialogFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(DialogFragment.STYLE_NORMAL, R.style.DialogFragmentTheme)
setStyle(STYLE_NO_TITLE, 0)
arguments?.run {
exam = getSerializable(ARGUMENT_KEY) as Exam
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
dialog.setTitle(getString(R.string.all_details))
return inflater.inflate(R.layout.dialog_exam, container, false)
}

View File

@ -11,11 +11,12 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.main.MainView
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_exam.*
import javax.inject.Inject
class ExamFragment : BaseFragment(), ExamView {
class ExamFragment : BaseFragment(), ExamView, MainView.MenuFragmentView {
@Inject
lateinit var presenter: ExamPresenter
@ -34,10 +35,8 @@ class ExamFragment : BaseFragment(), ExamView {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
presenter.run {
attachView(this@ExamFragment)
loadData(date = savedInstanceState?.getLong(SAVED_DATE_KEY))
}
messageContainer = examRecycler
presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY))
}
override fun initView() {
@ -48,9 +47,13 @@ class ExamFragment : BaseFragment(), ExamView {
layoutManager = SmoothScrollLinearLayoutManager(context)
adapter = examAdapter
}
examSwipe.setOnRefreshListener { presenter.loadData(date = null, forceRefresh = true) }
examPreviousButton.setOnClickListener { presenter.loadExamsForPreviousWeek() }
examNextButton.setOnClickListener { presenter.loadExamsForNextWeek()}
examSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
examPreviousButton.setOnClickListener { presenter.onPreviousWeek() }
examNextButton.setOnClickListener { presenter.onNextWeek() }
}
override fun hideRefresh() {
examSwipe.isRefreshing = false
}
override fun updateData(data: List<ExamItem>) {
@ -61,6 +64,16 @@ class ExamFragment : BaseFragment(), ExamView {
examNavDate.text = date
}
override fun clearData() {
examAdapter.clear()
}
override fun isViewEmpty() = examAdapter.isEmpty
override fun onFragmentReselected() {
presenter.onViewReselected()
}
override fun showEmpty(show: Boolean) {
examEmpty.visibility = if (show) VISIBLE else GONE
}
@ -73,10 +86,6 @@ class ExamFragment : BaseFragment(), ExamView {
examRecycler.visibility = if (show) VISIBLE else GONE
}
override fun showRefresh(show: Boolean) {
examSwipe.isRefreshing = show
}
override fun showPreButton(show: Boolean) {
examPreviousButton.visibility = if (show) VISIBLE else INVISIBLE
}
@ -95,7 +104,7 @@ class ExamFragment : BaseFragment(), ExamView {
}
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()
presenter.detachView()
}
}

View File

@ -12,15 +12,21 @@ import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.header_exam.*
import org.threeten.bp.LocalDate
class ExamHeader : AbstractHeaderItem<ExamHeader.ViewHolder>() {
class ExamHeader(private val date: LocalDate) : AbstractHeaderItem<ExamHeader.ViewHolder>() {
lateinit var date: LocalDate
override fun getLayoutRes() = R.layout.header_exam
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?): ViewHolder {
return ViewHolder(view, adapter)
}
override fun getLayoutRes() = R.layout.header_exam
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder,
position: Int, payloads: MutableList<Any>?) {
holder.run {
examHeaderDay.text = date.weekDayName.capitalize()
examHeaderDate.text = date.toFormattedString()
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
@ -37,21 +43,9 @@ class ExamHeader : AbstractHeaderItem<ExamHeader.ViewHolder>() {
return date.hashCode()
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder,
position: Int, payloads: MutableList<Any>?) {
holder.run {
examHeaderDay.text = date.weekDayName.capitalize()
examHeaderDate.text = date.toFormattedString()
}
}
class ViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?) : ExpandableViewHolder(view, adapter),
LayoutContainer {
init {
contentView.setOnClickListener(this)
}
override val containerView: View
get() = contentView
}

View File

@ -13,21 +13,6 @@ import kotlinx.android.synthetic.main.item_exam.*
class ExamItem(header: ExamHeader, val exam: Exam) : AbstractSectionableItem<ExamItem.ViewHolder, ExamHeader>(header) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ExamItem
if (exam != other.exam) return false
return true
}
override fun hashCode(): Int {
return exam.hashCode()
}
override fun getLayoutRes() = R.layout.item_exam
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): ViewHolder {
@ -43,6 +28,21 @@ class ExamItem(header: ExamHeader, val exam: Exam) : AbstractSectionableItem<Exa
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ExamItem
if (exam != other.exam) return false
return true
}
override fun hashCode(): Int {
return exam.hashCode()
}
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter),
LayoutContainer {

View File

@ -3,15 +3,15 @@ package io.github.wulkanowy.ui.main.exam
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.repositories.ExamRepository
import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.isHolidays
import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.schedulers.SchedulersManager
import io.github.wulkanowy.utils.toFormattedString
import io.github.wulkanowy.utils.weekFirstDayNextOnWeekEnd
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDate.now
import org.threeten.bp.LocalDate.ofEpochDay
import java.util.concurrent.TimeUnit.MILLISECONDS
import javax.inject.Inject
class ExamPresenter @Inject constructor(
@ -21,79 +21,89 @@ class ExamPresenter @Inject constructor(
private val sessionRepository: SessionRepository
) : BasePresenter<ExamView>(errorHandler) {
var currentDate: LocalDate = LocalDate.now().weekFirstDayNextOnWeekEnd
lateinit var currentDate: LocalDate
private set
override fun attachView(view: ExamView) {
super.attachView(view)
fun onAttachView(view: ExamView, date: Long?) {
super.onAttachView(view)
view.initView()
loadData(ofEpochDay(date ?: now().nextOrSameSchoolDay.toEpochDay()))
reloadView()
}
fun loadExamsForPreviousWeek() = loadData(currentDate.minusDays(7).toEpochDay())
fun loadExamsForNextWeek() = loadData(currentDate.plusDays(7).toEpochDay())
fun loadData(date: Long?, forceRefresh: Boolean = false) {
this.currentDate = LocalDate.ofEpochDay(date
?: currentDate.weekFirstDayNextOnWeekEnd.toEpochDay())
if (currentDate.isHolidays) return
disposable.clear()
disposable.add(sessionRepository.getSemesters()
.map { selectSemester(it, -1) }
.flatMap { examRepository.getExams(it, currentDate, currentDate.plusDays(4), forceRefresh) }
.map { it.groupBy { exam -> exam.date }.toSortedMap() }
.map { createExamItems(it) }
.subscribeOn(schedulers.backgroundThread())
.observeOn(schedulers.mainThread())
.doOnSubscribe {
view?.run {
showRefresh(forceRefresh)
showProgress(!forceRefresh)
if (!forceRefresh) showEmpty(false)
showContent(null == date && forceRefresh)
showPreButton(!currentDate.minusDays(7).isHolidays)
showNextButton(!currentDate.plusDays(7).isHolidays)
updateNavigationWeek(currentDate.toFormattedString("dd.MM") +
"-${currentDate.plusDays(4).toFormattedString("dd.MM")}")
}
}
.doAfterSuccess {
view?.run {
showEmpty(it.isEmpty())
showContent(it.isNotEmpty())
}
}
.doFinally {
view?.run {
showRefresh(false)
showProgress(false)
}
}
.subscribe({ view?.updateData(it) }) { errorHandler.proceed(it) })
fun onPreviousWeek() {
loadData(currentDate.minusDays(7))
reloadView()
}
private fun createExamItems(items: Map<LocalDate, List<Exam>>): List<ExamItem> {
return items.flatMap {
val header = ExamHeader().apply { date = it.key }
it.value.reversed().map { item ->
ExamItem(header, item)
}
fun onNextWeek() {
loadData(currentDate.plusDays(7))
reloadView()
}
fun onSwipeRefresh() {
loadData(currentDate, true)
}
fun onExamItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is ExamItem) view?.showExamDialog(item.exam)
}
private fun selectSemester(semesters: List<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
fun onViewReselected() {
loadData(now().nextOrSameSchoolDay)
reloadView()
}
private fun loadData(date: LocalDate, forceRefresh: Boolean = false) {
currentDate = date
disposable.apply {
clear()
add(sessionRepository.getSemesters()
.delay(200, MILLISECONDS)
.map { it.single { semester -> semester.current } }
.flatMap {
examRepository.getExams(it, currentDate.monday, currentDate.friday, forceRefresh)
}.map { it.groupBy { exam -> exam.date }.toSortedMap() }
.map { createExamItems(it) }
.subscribeOn(schedulers.backgroundThread())
.observeOn(schedulers.mainThread())
.doFinally {
view?.run {
hideRefresh()
showProgress(false)
}
}
.subscribe({
view?.apply {
updateData(it)
showEmpty(it.isEmpty())
showContent(it.isNotEmpty())
}
}) {
view?.run { showEmpty(isViewEmpty()) }
errorHandler.proceed(it)
})
}
}
private fun createExamItems(items: Map<LocalDate, List<Exam>>): List<ExamItem> {
return items.flatMap {
ExamHeader(it.key).let { header ->
it.value.reversed().map { item -> ExamItem(header, item) }
}
}
}
private fun reloadView() {
view?.apply {
showProgress(true)
showContent(false)
showEmpty(false)
clearData()
showPreButton(!currentDate.minusDays(7).isHolidays)
showNextButton(!currentDate.plusDays(7).isHolidays)
updateNavigationWeek("${currentDate.toFormattedString("dd.MM")} - " +
currentDate.plusDays(4).toFormattedString("dd.MM"))
}
}
}

View File

@ -9,19 +9,23 @@ interface ExamView : BaseView {
fun updateData(data: List<ExamItem>)
fun updateNavigationWeek(date: String)
fun clearData()
fun isViewEmpty(): Boolean
fun hideRefresh()
fun showEmpty(show: Boolean)
fun showProgress(show: Boolean)
fun showContent(show: Boolean)
fun showRefresh(show: Boolean)
fun showNextButton(show: Boolean)
fun showPreButton(show: Boolean)
fun showExamDialog(exam: Exam)
fun updateNavigationWeek(date: String)
}

View File

@ -24,6 +24,8 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MenuFragmentView {
lateinit var pagerAdapter: BasePagerAdapter
companion object {
private const val SAVED_SEMESTER_KEY = "CURRENT_SEMESTER"
fun newInstance() = GradeFragment()
}
@ -38,7 +40,7 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MenuFragmentView {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
presenter.attachView(this)
presenter.onAttachView(this, savedInstanceState?.getInt(SAVED_SEMESTER_KEY))
}
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
@ -113,8 +115,13 @@ class GradeFragment : BaseFragment(), GradeView, MainView.MenuFragmentView {
(pagerAdapter.registeredFragments[index] as? GradeView.GradeChildView)?.onParentChangeSemester()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(SAVED_SEMESTER_KEY, presenter.selectedIndex)
}
override fun onDestroyView() {
super.onDestroyView()
presenter.detachView()
presenter.onDetachView()
}
}

View File

@ -6,7 +6,7 @@ import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.schedulers.SchedulersManager
import io.reactivex.Completable
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeUnit.MILLISECONDS
import javax.inject.Inject
class GradePresenter @Inject constructor(
@ -14,16 +14,18 @@ class GradePresenter @Inject constructor(
private val schedulers: SchedulersManager,
private val sessionRepository: SessionRepository) : BasePresenter<GradeView>(errorHandler) {
private var semesters = emptyList<Semester>()
var selectedIndex = 0
private set
private var selectedIndex = 0
private var semesters = emptyList<Semester>()
private val loadedSemesterId = mutableMapOf<Int, String>()
override fun attachView(view: GradeView) {
super.attachView(view)
disposable.add(Completable.timer(150, TimeUnit.MILLISECONDS, schedulers.mainThread())
fun onAttachView(view: GradeView, savedIndex: Int?) {
super.onAttachView(view)
disposable.add(Completable.timer(150, MILLISECONDS, schedulers.mainThread())
.subscribe {
selectedIndex = savedIndex ?: 0
view.initView()
loadData()
})
@ -34,13 +36,13 @@ class GradePresenter @Inject constructor(
}
fun onSemesterSwitch(): Boolean {
if (semesters.isNotEmpty()) view?.showSemesterDialog(selectedIndex)
if (semesters.isNotEmpty()) view?.showSemesterDialog(selectedIndex - 1)
return true
}
fun onSemesterSelected(index: Int) {
if (selectedIndex != index) {
selectedIndex = index
if (selectedIndex != index - 1) {
selectedIndex = index + 1
loadedSemesterId.clear()
view?.let {
notifyChildrenSemesterChange()
@ -67,9 +69,9 @@ class GradePresenter @Inject constructor(
private fun loadData() {
disposable.add(sessionRepository.getSemesters()
.map {
.doOnSuccess {
it.first { item -> item.current }.also { current ->
selectedIndex = current.semesterName - 1
selectedIndex = if (selectedIndex == 0) current.semesterName else selectedIndex
semesters = it.filter { semester -> semester.diaryId == current.diaryId }
}
}
@ -81,7 +83,7 @@ class GradePresenter @Inject constructor(
}
private fun loadChild(index: Int, forceRefresh: Boolean = false) {
semesters.first { it.semesterName == selectedIndex + 1 }.semesterId.also {
semesters.first { it.semesterName == selectedIndex }.semesterId.also {
if (forceRefresh || loadedSemesterId[index] != it) {
view?.notifyChildLoadData(index, it, forceRefresh)
}

View File

@ -30,6 +30,7 @@ class GradeDetailsDialog : DialogFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
arguments?.run {
grade = getSerializable(ARGUMENT_KEY) as Grade
}
@ -48,7 +49,7 @@ class GradeDetailsDialog : DialogFragment() {
gradeDialogColorValue.text = getString(grade.colorStringId)
gradeDialogCommentValue.apply {
if (grade.comment.isEmpty()) {
if (grade.comment.isBlank()) {
visibility = GONE
gradeDialogComment.visibility = GONE
} else text = grade.comment
@ -59,15 +60,15 @@ class GradeDetailsDialog : DialogFragment() {
setBackgroundResource(grade.valueColor)
}
gradeDialogTeacherValue.text = if (grade.teacher.isEmpty()) {
gradeDialogTeacherValue.text = if (grade.teacher.isBlank()) {
getString(R.string.all_no_data)
} else grade.teacher
gradeDialogDescriptionValue.text = grade.run {
when {
description.isEmpty() && gradeSymbol.isNotEmpty() -> gradeSymbol
description.isEmpty() && gradeSymbol.isEmpty() -> getString(R.string.all_no_description)
gradeSymbol.isNotEmpty() && description.isNotEmpty() -> "$gradeSymbol - $description"
description.isBlank() && gradeSymbol.isNotBlank() -> gradeSymbol
description.isBlank() && gradeSymbol.isBlank() -> getString(R.string.all_no_description)
gradeSymbol.isNotBlank() && description.isNotBlank() -> "$gradeSymbol - $description"
else -> description
}
}

View File

@ -37,7 +37,8 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
presenter.attachView(this)
messageContainer = gradeDetailsRecycler
presenter.onAttachView(this)
}
override fun initView() {
@ -47,6 +48,8 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh
setOnItemClickListener { presenter.onGradeItemSelected(getItem(it)) }
}
gradeDetailsAdapter.getItemCountOfTypes()
gradeDetailsRecycler.run {
layoutManager = SmoothScrollLinearLayoutManager(context)
adapter = gradeDetailsAdapter
@ -100,7 +103,7 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh
}
override fun onParentLoadData(semesterId: String, forceRefresh: Boolean) {
presenter.loadData(semesterId, forceRefresh)
presenter.onParentViewLoadData(semesterId, forceRefresh)
}
override fun onParentReselected() {
@ -108,7 +111,7 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh
}
override fun onParentChangeSemester() {
presenter.onParentChangeSemester()
presenter.onParentViewChangeSemester()
}
override fun notifyParentDataLoaded(semesterId: String) {
@ -129,6 +132,6 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh
override fun onDestroyView() {
super.onDestroyView()
presenter.detachView()
presenter.onDetachView()
}
}

View File

@ -31,7 +31,7 @@ class GradeDetailsItem(val grade: Grade, private val weightString: String, priva
text = grade.entry
setBackgroundResource(valueColor)
}
gradeItemDescription.text = if (grade.description.isNotEmpty()) grade.description else grade.gradeSymbol
gradeItemDescription.text = if (grade.description.isNotBlank()) grade.description else grade.gradeSymbol
gradeItemDate.text = grade.date.toFormattedString()
gradeItemWeight.text = "$weightString: ${grade.weight}"
gradeItemNote.visibility = if (grade.isNew) VISIBLE else GONE

View File

@ -17,12 +17,12 @@ class GradeDetailsPresenter @Inject constructor(
private val gradeRepository: GradeRepository,
private val sessionRepository: SessionRepository) : BasePresenter<GradeDetailsView>(errorHandler) {
override fun attachView(view: GradeDetailsView) {
super.attachView(view)
override fun onAttachView(view: GradeDetailsView) {
super.onAttachView(view)
view.initView()
}
fun loadData(semesterId: String, forceRefresh: Boolean) {
fun onParentViewLoadData(semesterId: String, forceRefresh: Boolean) {
disposable.add(sessionRepository.getSemesters()
.flatMap { gradeRepository.getGrades(it.first { item -> item.semesterId == semesterId }, forceRefresh) }
.map { createGradeItems(it.groupBy { grade -> grade.subject }.toSortedMap()) }
@ -76,7 +76,7 @@ class GradeDetailsPresenter @Inject constructor(
}
}
fun onParentChangeSemester() {
fun onParentViewChangeSemester() {
view?.run {
showProgress(true)
showRefresh(false)

View File

@ -33,7 +33,8 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
presenter.attachView(this)
messageContainer = gradeSummaryRecycler
presenter.onAttachView(this)
}
override fun initView() {
@ -81,7 +82,7 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh
}
override fun onParentLoadData(semesterId: String, forceRefresh: Boolean) {
presenter.loadData(semesterId, forceRefresh)
presenter.onParentViewLoadData(semesterId, forceRefresh)
}
override fun onParentReselected() {
@ -89,7 +90,7 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh
}
override fun onParentChangeSemester() {
presenter.onParentChangeSemester()
presenter.onParentViewChangeSemester()
}
override fun notifyParentDataLoaded(semesterId: String) {
@ -106,6 +107,6 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh
override fun onDestroyView() {
super.onDestroyView()
presenter.detachView()
presenter.onDetachView()
}
}

View File

@ -20,12 +20,12 @@ class GradeSummaryPresenter @Inject constructor(
private val schedulers: SchedulersManager)
: BasePresenter<GradeSummaryView>(errorHandler) {
override fun attachView(view: GradeSummaryView) {
super.attachView(view)
override fun onAttachView(view: GradeSummaryView) {
super.onAttachView(view)
view.initView()
}
fun loadData(semesterId: String, forceRefresh: Boolean) {
fun onParentViewLoadData(semesterId: String, forceRefresh: Boolean) {
disposable.add(sessionRepository.getSemesters()
.map { semester -> semester.first { it.semesterId == semesterId } }
.flatMap {
@ -76,7 +76,7 @@ class GradeSummaryPresenter @Inject constructor(
}
}
fun onParentChangeSemester() {
fun onParentViewChangeSemester() {
view?.run {
showProgress(true)
showRefresh(false)
@ -110,12 +110,12 @@ class GradeSummaryPresenter @Inject constructor(
private fun checkEmpty(gradeSummary: GradeSummary, averages: Map<String, Double>): Boolean {
return gradeSummary.run {
finalGrade.isEmpty() && predictedGrade.isEmpty() && averages[subject] == null
finalGrade.isBlank() && predictedGrade.isBlank() && averages[subject] == null
}
}
private fun formatAverage(average: Double, defaultValue: String = "-- --"): String {
return if (average == 0.0 || average.isNaN()) defaultValue
return if (average == 0.0) defaultValue
else format(FRANCE, "%.2f", average)
}
}

View File

@ -28,14 +28,13 @@ class TimetableDialog : DialogFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(DialogFragment.STYLE_NORMAL, R.style.DialogFragmentTheme)
setStyle(STYLE_NO_TITLE, 0)
arguments?.run {
lesson = getSerializable(ARGUMENT_KEY) as Timetable
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
dialog.setTitle(getString(R.string.all_details))
return inflater.inflate(R.layout.dialog_timetable, container, false)
}

View File

@ -10,11 +10,12 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.main.MainView
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_timetable.*
import javax.inject.Inject
class TimetableFragment : BaseFragment(), TimetableView {
class TimetableFragment : BaseFragment(), TimetableView, MainView.MenuFragmentView {
@Inject
lateinit var presenter: TimetablePresenter
@ -24,6 +25,7 @@ class TimetableFragment : BaseFragment(), TimetableView {
companion object {
private const val SAVED_DATE_KEY = "CURRENT_DATE"
fun newInstance() = TimetableFragment()
}
@ -33,25 +35,22 @@ class TimetableFragment : BaseFragment(), TimetableView {
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
presenter.run {
attachView(this@TimetableFragment)
loadData(date = savedInstanceState?.getLong(SAVED_DATE_KEY))
}
messageContainer = timetableRecycler
presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY))
}
override fun initView() {
timetableAdapter.run {
isAutoCollapseOnExpand = true
isAutoScrollOnExpand = true
setOnItemClickListener { presenter.onTimetableItemSelected(getItem(it))}
setOnItemClickListener { presenter.onTimetableItemSelected(getItem(it)) }
}
timetableRecycler.run {
layoutManager = SmoothScrollLinearLayoutManager(context)
adapter = timetableAdapter
}
timetableSwipe.setOnRefreshListener { presenter.loadData(date = null, forceRefresh = true) }
timetablePreviousButton.setOnClickListener { presenter.loadTimetableForPreviousDay() }
timetableNextButton.setOnClickListener { presenter.loadTimetableForNextDay() }
timetableSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
timetablePreviousButton.setOnClickListener { presenter.onPreviousDay() }
timetableNextButton.setOnClickListener { presenter.onNextDay() }
}
override fun updateData(data: List<TimetableItem>) {
@ -68,6 +67,14 @@ class TimetableFragment : BaseFragment(), TimetableView {
override fun isViewEmpty() = timetableAdapter.isEmpty
override fun hideRefresh() {
timetableSwipe.isRefreshing = false
}
override fun onFragmentReselected() {
presenter.onViewReselected()
}
override fun showEmpty(show: Boolean) {
timetableEmpty.visibility = if (show) View.VISIBLE else View.GONE
}
@ -80,10 +87,6 @@ class TimetableFragment : BaseFragment(), TimetableView {
timetableRecycler.visibility = if (show) View.VISIBLE else View.GONE
}
override fun showRefresh(show: Boolean) {
timetableSwipe.isRefreshing = show
}
override fun showPreButton(show: Boolean) {
timetablePreviousButton.visibility = if (show) View.VISIBLE else View.INVISIBLE
}
@ -96,13 +99,15 @@ class TimetableFragment : BaseFragment(), TimetableView {
TimetableDialog.newInstance(lesson).show(fragmentManager, lesson.toString())
}
override fun roomString() = getString(R.string.timetable_room)
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay())
}
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()
presenter.detachView()
}
}

View File

@ -15,15 +15,29 @@ import io.github.wulkanowy.utils.toFormattedString
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_timetable.*
class TimetableItem : AbstractFlexibleItem<TimetableItem.ViewHolder>() {
class TimetableItem(val lesson: Timetable, private val roomText: String)
: AbstractFlexibleItem<TimetableItem.ViewHolder>() {
lateinit var lesson: Timetable
override fun getLayoutRes(): Int = R.layout.item_timetable
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
return ViewHolder(view, adapter)
}
override fun getLayoutRes(): Int = R.layout.item_timetable
@SuppressLint("SetTextI18n")
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder,
position: Int, payloads: MutableList<Any>?) {
holder.apply {
timetableItemNumber.text = lesson.number.toString()
timetableItemSubject.text = lesson.subject
timetableItemRoom.text = if (lesson.room.isNotBlank()) "$roomText ${lesson.room}" else ""
timetableItemTime.text = "${lesson.start.toFormattedString("HH:mm")} - ${lesson.end.toFormattedString("HH:mm")}"
timetableItemAlert.visibility = if (lesson.changes || lesson.canceled) VISIBLE else GONE
timetableItemSubject.paintFlags =
if (lesson.canceled) timetableItemSubject.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
else timetableItemSubject.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
@ -39,27 +53,10 @@ class TimetableItem : AbstractFlexibleItem<TimetableItem.ViewHolder>() {
return lesson.hashCode()
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder,
position: Int, payloads: MutableList<Any>?) {
holder.bind(lesson)
}
class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter),
LayoutContainer {
override val containerView: View
get() = contentView
@SuppressLint("SetTextI18n")
fun bind(lesson: Timetable) {
timetableItemNumber.text = lesson.number.toString()
timetableItemSubject.text = lesson.subject
timetableItemRoom.text = if (lesson.room.isNotBlank()) "${view.context.getString(R.string.timetable_room)} ${lesson.room}" else ""
timetableItemTime.text = "${lesson.start.toFormattedString("HH:mm")} - ${lesson.end.toFormattedString("HH:mm")}"
timetableItemAlert.visibility = if (lesson.changes || lesson.canceled) VISIBLE else GONE
timetableItemSubject.paintFlags =
if (lesson.canceled) timetableItemSubject.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
else timetableItemSubject.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
}
}
}

View File

@ -2,14 +2,15 @@ package io.github.wulkanowy.ui.main.timetable
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.data.repositories.TimetableRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.*
import io.github.wulkanowy.utils.schedulers.SchedulersManager
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDate.now
import org.threeten.bp.LocalDate.ofEpochDay
import java.util.concurrent.TimeUnit.MILLISECONDS
import javax.inject.Inject
class TimetablePresenter @Inject constructor(
@ -19,75 +20,78 @@ class TimetablePresenter @Inject constructor(
private val sessionRepository: SessionRepository
) : BasePresenter<TimetableView>(errorHandler) {
var currentDate: LocalDate = LocalDate.now().nearSchoolDayNextOnWeekEnd
lateinit var currentDate: LocalDate
private set
override fun attachView(view: TimetableView) {
super.attachView(view)
fun onAttachView(view: TimetableView, date: Long?) {
super.onAttachView(view)
view.initView()
loadData(ofEpochDay(date ?: now().nextOrSameSchoolDay.toEpochDay()))
reloadView()
}
fun loadTimetableForPreviousDay() = loadData(currentDate.previousWorkDay.toEpochDay())
fun loadTimetableForNextDay() = loadData(currentDate.nextWorkDay.toEpochDay())
fun loadData(date: Long?, forceRefresh: Boolean = false) {
this.currentDate = LocalDate.ofEpochDay(date ?: currentDate.nearSchoolDayNextOnWeekEnd.toEpochDay())
if (currentDate.isHolidays) return
disposable.clear()
disposable.add(sessionRepository.getSemesters()
.map { selectSemester(it, -1) }
.flatMap { timetableRepository.getTimetable(it, currentDate, currentDate, forceRefresh) }
.map { createTimetableItems(it) }
.subscribeOn(schedulers.backgroundThread())
.observeOn(schedulers.mainThread())
.doOnSubscribe {
view?.run {
showRefresh(forceRefresh)
showProgress(!forceRefresh)
if (!forceRefresh) clearData()
showPreButton(!currentDate.minusDays(1).isHolidays)
showNextButton(!currentDate.plusDays(1).isHolidays)
updateNavigationDay(currentDate.toFormattedString("EEEE \n dd.MM.YYYY").capitalize())
}
}
.doFinally {
view?.run {
showRefresh(false)
showProgress(false)
}
}
.subscribe({
view?.run {
showEmpty(it.isEmpty())
showContent(it.isNotEmpty())
updateData(it)
}
}) {
view?.run { showEmpty(isViewEmpty()) }
errorHandler.proceed(it)
})
fun onPreviousDay() {
loadData(currentDate.previousSchoolDay)
reloadView()
}
private fun createTimetableItems(items: List<Timetable>): List<TimetableItem> {
return items.map {
TimetableItem().apply { lesson = it }
fun onNextDay() {
loadData(currentDate.nextSchoolDay)
reloadView()
}
fun onSwipeRefresh() {
loadData(currentDate, true)
}
fun onViewReselected() {
loadData(now().nextOrSameSchoolDay)
reloadView()
}
fun onTimetableItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is TimetableItem) view?.showTimetableDialog(item.lesson)
}
private fun selectSemester(semesters: List<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
private fun loadData(date: LocalDate, forceRefresh: Boolean = false) {
currentDate = date
disposable.apply {
clear()
add(sessionRepository.getSemesters()
.delay(200, MILLISECONDS)
.map { it.single { semester -> semester.current } }
.flatMap { timetableRepository.getTimetable(it, currentDate, currentDate, forceRefresh) }
.map { items -> items.map { TimetableItem(it, view?.roomString().orEmpty()) } }
.subscribeOn(schedulers.backgroundThread())
.observeOn(schedulers.mainThread())
.doFinally {
view?.run {
hideRefresh()
showProgress(false)
}
}
.subscribe({
view?.apply {
updateData(it)
showEmpty(it.isEmpty())
showContent(it.isNotEmpty())
}
}) {
view?.run { showEmpty(isViewEmpty()) }
errorHandler.proceed(it)
})
}
}
private fun reloadView() {
view?.apply {
showProgress(true)
showContent(false)
showEmpty(false)
clearData()
showNextButton(!currentDate.plusDays(1).isHolidays)
showPreButton(!currentDate.minusDays(1).isHolidays)
updateNavigationDay(currentDate.toFormattedString("EEEE \n dd.MM.YYYY").capitalize())
}
}
}

View File

@ -11,20 +11,23 @@ interface TimetableView : BaseView {
fun updateNavigationDay(date: String)
fun isViewEmpty(): Boolean
fun clearData()
fun hideRefresh()
fun showEmpty(show: Boolean)
fun showProgress(show: Boolean)
fun showContent(show: Boolean)
fun showRefresh(show: Boolean)
fun showPreButton(show: Boolean)
fun showNextButton(show: Boolean)
fun showTimetableDialog(lesson: Timetable)
fun isViewEmpty(): Boolean
fun clearData()
fun roomString(): String
}

View File

@ -13,12 +13,7 @@ class SplashActivity : BaseActivity(), SplashView {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
presenter.attachView(this)
}
override fun onDestroy() {
super.onDestroy()
presenter.detachView()
presenter.onAttachView(this)
}
override fun openLoginView() {
@ -30,4 +25,9 @@ class SplashActivity : BaseActivity(), SplashView {
startActivity(MainActivity.getStartIntent(this))
finish()
}
override fun onDestroy() {
presenter.onDetachView()
super.onDestroy()
}
}

View File

@ -9,8 +9,8 @@ class SplashPresenter @Inject constructor(private val sessionRepository: Session
errorHandler: ErrorHandler)
: BasePresenter<SplashView>(errorHandler) {
override fun attachView(view: SplashView) {
super.attachView(view)
override fun onAttachView(view: SplashView) {
super.onAttachView(view)
view.run { if (sessionRepository.isSessionSaved) openMainView() else openLoginView() }
}
}

View File

@ -19,7 +19,7 @@ fun List<Grade>.calcAverage(): Double {
fun List<GradeSummary>.calcAverage(): Double {
return asSequence().mapNotNull {
if (it.finalGrade.matches("[0-6]".toRegex())) it.finalGrade.toDouble() else null
}.average()
}.average().let { if (it.isNaN()) 0.0 else it }
}
inline val Grade.valueColor: Int
@ -43,6 +43,7 @@ inline val Grade.colorStringId: Int
"F04C4C" -> R.string.all_red
"20A4F7" -> R.string.all_blue
"6ECD07" -> R.string.all_green
"B16CF1" -> R.string.all_purple
else -> R.string.all_empty_color
}
}

View File

@ -1,23 +1,24 @@
package io.github.wulkanowy.utils
import org.threeten.bp.DayOfWeek.*
import org.threeten.bp.Instant
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDateTime
import org.threeten.bp.ZoneId
import org.threeten.bp.format.DateTimeFormatter
import org.threeten.bp.format.DateTimeFormatter.ofPattern
import org.threeten.bp.temporal.TemporalAdjusters
import org.threeten.bp.temporal.TemporalAdjusters.*
import java.text.SimpleDateFormat
import java.util.*
private const val DATE_PATTERN = "yyyy-MM-dd"
private const val DATE_PATTERN = "dd.MM.yyyy"
fun Date.toLocalDate(): LocalDate {
return LocalDate.parse(SimpleDateFormat(DATE_PATTERN, Locale.getDefault()).format(this))
return Instant.ofEpochMilli(this.time).atZone(ZoneId.systemDefault()).toLocalDate()
}
fun Date.toLocalDateTime(): LocalDateTime = LocalDateTime.parse(SimpleDateFormat("yyyy-MM-dd HH:mm:ss",
Locale.getDefault()).format(this), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
fun Date.toLocalDateTime(): LocalDateTime {
return Instant.ofEpochMilli(this.time).atZone(ZoneId.systemDefault()).toLocalDateTime()
}
fun String.toLocalDate(format: String = DATE_PATTERN): LocalDate {
return LocalDate.parse(this, DateTimeFormatter.ofPattern(format))
@ -25,9 +26,9 @@ fun String.toLocalDate(format: String = DATE_PATTERN): LocalDate {
fun LocalDate.toFormattedString(format: String = DATE_PATTERN): String = this.format(ofPattern(format))
fun LocalDateTime.toFormattedString(format: String = DATE_PATTERN): String = this.format(DateTimeFormatter.ofPattern(format))
fun LocalDateTime.toFormattedString(format: String = DATE_PATTERN): String = this.format(ofPattern(format))
inline val LocalDate.nextWorkDay: LocalDate
inline val LocalDate.nextSchoolDay: LocalDate
get() {
return when (this.dayOfWeek) {
FRIDAY, SATURDAY, SUNDAY -> this.with(next(MONDAY))
@ -35,7 +36,7 @@ inline val LocalDate.nextWorkDay: LocalDate
}
}
inline val LocalDate.previousWorkDay: LocalDate
inline val LocalDate.previousSchoolDay: LocalDate
get() {
return when (this.dayOfWeek) {
SATURDAY, SUNDAY, MONDAY -> this.with(previous(FRIDAY))
@ -43,7 +44,15 @@ inline val LocalDate.previousWorkDay: LocalDate
}
}
inline val LocalDate.nearSchoolDayPrevOnWeekEnd: LocalDate
inline val LocalDate.nextOrSameSchoolDay: LocalDate
get() {
return when (this.dayOfWeek) {
SATURDAY, SUNDAY -> this.with(next(MONDAY))
else -> this
}
}
inline val LocalDate.previousOrSameSchoolDay: LocalDate
get() {
return when (this.dayOfWeek) {
SATURDAY, SUNDAY -> this.with(previous(FRIDAY))
@ -51,27 +60,14 @@ inline val LocalDate.nearSchoolDayPrevOnWeekEnd: LocalDate
}
}
inline val LocalDate.nearSchoolDayNextOnWeekEnd: LocalDate
get() {
return when (this.dayOfWeek) {
SATURDAY, SUNDAY -> this.with(next(MONDAY))
else -> this
}
}
inline val LocalDate.weekDayName: String
get() = this.format(ofPattern("EEEE", Locale.getDefault()))
inline val LocalDate.weekFirstDayAlwaysCurrent: LocalDate
get() = this.with(TemporalAdjusters.previousOrSame(MONDAY))
inline val LocalDate.monday: LocalDate
get() = this.with(MONDAY)
inline val LocalDate.weekFirstDayNextOnWeekEnd: LocalDate
get() {
return when (this.dayOfWeek) {
SATURDAY, SUNDAY -> this.with(next(MONDAY))
else -> this.with(previousOrSame(MONDAY))
}
}
inline val LocalDate.friday: LocalDate
get() = this.with(FRIDAY)
/**
* [Dz.U. 2016 poz. 1335](http://prawo.sejm.gov.pl/isap.nsf/DocDetails.xsp?id=WDU20160001335)

View File

@ -11,7 +11,7 @@ import android.security.KeyPairGeneratorSpec
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties.*
import android.util.Base64
import android.util.Base64.DEFAULT
import android.util.Base64.*
import timber.log.Timber
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
@ -20,8 +20,8 @@ import java.nio.charset.Charset
import java.security.KeyPairGenerator
import java.security.KeyStore
import java.security.PrivateKey
import java.security.PublicKey
import java.util.*
import java.util.Calendar.YEAR
import javax.crypto.Cipher
import javax.crypto.Cipher.DECRYPT_MODE
import javax.crypto.Cipher.ENCRYPT_MODE
@ -30,24 +30,33 @@ import javax.crypto.CipherOutputStream
import javax.security.auth.x500.X500Principal
import kotlin.collections.ArrayList
object Scrambler {
private const val KEY_ALIAS = "USER_PASSWORD"
private const val KEY_ALIAS = "USER_PASSWORD"
private const val ALGORITHM_RSA = "RSA"
private const val ALGORITHM_RSA = "RSA"
private const val KEYSTORE_NAME = "AndroidKeyStore"
private const val KEYSTORE_NAME = "AndroidKeyStore"
private const val KEY_TRANSFORMATION_ALGORITHM = "RSA/ECB/PKCS1Padding"
private const val KEY_TRANSFORMATION_ALGORITHM = "RSA/ECB/PKCS1Padding"
private const val KEY_CIPHER_JELLY_PROVIDER = "AndroidOpenSSL"
private const val KEY_CIPHER_JELLY_PROVIDER = "AndroidOpenSSL"
private const val KEY_CIPHER_M_PROVIDER = "AndroidKeyStoreBCWorkaround"
private const val KEY_CIPHER_M_PROVIDER = "AndroidKeyStoreBCWorkaround"
private val KEY_CHARSET = Charset.forName("UTF-8")
private val KEY_CHARSET = Charset.forName("UTF-8")
private val isKeyPairExists: Boolean
get() = keyStore.getKey(KEY_ALIAS, null) != null
@JvmStatic
fun encrypt(plainText: String, context: Context): String {
private val cipher: Cipher
get() {
return if (SDK_INT >= M) Cipher.getInstance(KEY_TRANSFORMATION_ALGORITHM, KEY_CIPHER_M_PROVIDER)
else Cipher.getInstance(KEY_TRANSFORMATION_ALGORITHM, KEY_CIPHER_JELLY_PROVIDER)
}
private val keyStore: KeyStore
get() = KeyStore.getInstance(KEYSTORE_NAME).apply { load(null) }
fun encrypt(plainText: String, context: Context): String {
if (plainText.isEmpty()) throw ScramblerException("Text to be encrypted is empty")
if (SDK_INT < JELLY_BEAN_MR2) {
@ -55,79 +64,59 @@ object Scrambler {
}
return try {
if (!isKeyPairExist()) generateKeyPair(context)
if (!isKeyPairExists) generateKeyPair(context)
cipher.let {
it.init(ENCRYPT_MODE, keyStore.getCertificate(KEY_ALIAS).publicKey)
val cipher = getCipher()
cipher.init(ENCRYPT_MODE, getPublicKey())
val outputStream = ByteArrayOutputStream()
val cipherOutputStream = CipherOutputStream(outputStream, cipher)
cipherOutputStream.write(plainText.toByteArray(KEY_CHARSET))
cipherOutputStream.close()
Base64.encodeToString(outputStream.toByteArray(), DEFAULT)
ByteArrayOutputStream().let { output ->
CipherOutputStream(output, it).apply {
write(plainText.toByteArray(KEY_CHARSET))
close()
}
encodeToString(output.toByteArray(), DEFAULT)
}
}
} catch (exception: Exception) {
Timber.e(exception, "An error occurred while encrypting text")
String(Base64.encode(plainText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET)
String(encode(plainText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET)
}
}
}
@JvmStatic
fun decrypt(cipherText: String): String {
fun decrypt(cipherText: String): String {
if (cipherText.isEmpty()) throw ScramblerException("Text to be encrypted is empty")
if (SDK_INT < JELLY_BEAN_MR2 || cipherText.length < 250) {
return String(Base64.decode(cipherText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET)
return String(decode(cipherText.toByteArray(KEY_CHARSET), DEFAULT), KEY_CHARSET)
}
if (!isKeyPairExist()) throw ScramblerException("KeyPair doesn't exist")
if (!isKeyPairExists) throw ScramblerException("KeyPair doesn't exist")
try {
val cipher = getCipher()
cipher.init(DECRYPT_MODE, getPrivateKey())
return try {
cipher.let {
it.init(DECRYPT_MODE, (keyStore.getKey(KEY_ALIAS, null) as PrivateKey))
val input = CipherInputStream(ByteArrayInputStream(Base64.decode(cipherText, DEFAULT)), cipher)
CipherInputStream(ByteArrayInputStream(decode(cipherText, DEFAULT)), it).let { input ->
val values = ArrayList<Byte>()
var nextByte = 0
while ({ nextByte = input.read(); nextByte }() != -1) {
values.add(nextByte.toByte())
}
val bytes = ByteArray(values.size)
for (i in bytes.indices) {
bytes[i] = values[i]
}
return String(bytes, 0, bytes.size, KEY_CHARSET)
String(bytes, 0, bytes.size, KEY_CHARSET)
}
}
} catch (e: Exception) {
throw ScramblerException("An error occurred while decrypting text", e)
}
}
}
private fun getKeyStoreInstance(): KeyStore {
val keyStore = KeyStore.getInstance(KEYSTORE_NAME)
keyStore.load(null)
return keyStore
}
private fun getPublicKey(): PublicKey =
(getKeyStoreInstance().getEntry(KEY_ALIAS, null) as KeyStore.PrivateKeyEntry)
.certificate.publicKey
private fun getPrivateKey(): PrivateKey =
(getKeyStoreInstance().getEntry(KEY_ALIAS, null) as KeyStore.PrivateKeyEntry).privateKey
private fun getCipher(): Cipher {
return if (SDK_INT >= M) Cipher.getInstance(KEY_TRANSFORMATION_ALGORITHM, KEY_CIPHER_M_PROVIDER)
else Cipher.getInstance(KEY_TRANSFORMATION_ALGORITHM, KEY_CIPHER_JELLY_PROVIDER)
}
@TargetApi(JELLY_BEAN_MR2)
private fun generateKeyPair(context: Context) {
val spec = if (SDK_INT >= M) {
@TargetApi(JELLY_BEAN_MR2)
private fun generateKeyPair(context: Context) {
(if (SDK_INT >= M) {
KeyGenParameterSpec.Builder(KEY_ALIAS, PURPOSE_DECRYPT or PURPOSE_ENCRYPT)
.setDigests(DIGEST_SHA256, DIGEST_SHA512)
.setCertificateSubject(X500Principal("CN=Wulkanowy"))
@ -136,25 +125,19 @@ object Scrambler {
.setCertificateSerialNumber(BigInteger.TEN)
.build()
} else {
val start = Calendar.getInstance()
val end = Calendar.getInstance()
end.add(Calendar.YEAR, 99)
KeyPairGeneratorSpec.Builder(context)
.setAlias(KEY_ALIAS)
.setSubject(X500Principal("CN=Wulkanowy"))
.setSerialNumber(BigInteger.TEN)
.setStartDate(start.time)
.setEndDate(end.time)
.setStartDate(Calendar.getInstance().time)
.setEndDate(Calendar.getInstance().apply { add(YEAR, 99) }.time)
.build()
}).let {
KeyPairGenerator.getInstance(ALGORITHM_RSA, KEYSTORE_NAME).apply {
initialize(it)
genKeyPair()
}
}
val generator = KeyPairGenerator.getInstance(ALGORITHM_RSA, KEYSTORE_NAME)
generator.initialize(spec)
generator.generateKeyPair()
Timber.i("A new KeyPair has been generated")
}
private fun isKeyPairExist(): Boolean = getKeyStoreInstance().getKey(KEY_ALIAS, null) != null
}

View File

@ -4,17 +4,24 @@
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:minWidth="300dp"
android:orientation="vertical"
android:padding="20dp">
<TextView
android:id="@+id/attendance_dialog_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:text="@string/all_details"
android:textSize="20sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/all_subject"
android:textIsSelectable="true"
android:textSize="17sp" />
<TextView
@ -81,10 +88,8 @@
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="15dp"
android:padding="0dp"
android:text="@string/all_close"
android:textAllCaps="true"
android:textSize="15sp" />
</LinearLayout>
</ScrollView>

View File

@ -4,18 +4,25 @@
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:minWidth="300dp"
android:orientation="vertical"
android:padding="20dp">
<TextView
android:id="@+id/exam_dialog_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:text="@string/all_details"
android:textSize="20sp" />
<TextView
android:id="@+id/examDialogSubject"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/all_subject"
android:textIsSelectable="true"
android:textSize="17sp" />
<TextView
@ -33,7 +40,6 @@
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/exam_type"
android:textIsSelectable="true"
android:textSize="17sp" />
<TextView
@ -102,8 +108,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="15dp"
android:padding="0dp"
android:layout_marginTop="20dp"
android:text="@string/all_close"
android:textAllCaps="true"
android:textSize="15sp" />

View File

@ -4,12 +4,20 @@
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:minWidth="300dp"
android:orientation="vertical"
android:padding="20dp">
<TextView
android:id="@+id/exam_dialog_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:text="@string/all_details"
android:textSize="20sp" />
<TextView
android:id="@+id/timetableDialogChangesTitle"
android:layout_width="wrap_content"
@ -33,7 +41,6 @@
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/timetable_lesson"
android:textIsSelectable="true"
android:textSize="17sp" />
<TextView
@ -119,7 +126,6 @@
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="15dp"
android:padding="0dp"
android:text="@string/all_close"
android:textAllCaps="true"
android:textSize="15sp" />

View File

@ -1,15 +1,13 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<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"
android:orientation="vertical">
android:layout_height="match_parent">
<FrameLayout
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical">
android:layout_height="match_parent"
android:layout_marginBottom="50dp">
<ProgressBar
android:id="@+id/attendanceProgress"
@ -42,8 +40,8 @@
<android.support.v7.widget.AppCompatImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="100dp"
android:minWidth="100dp"
android:minHeight="100dp"
app:srcCompat="@drawable/ic_menu_main_attendance_24dp"
app:tint="?android:attr/textColorPrimary"
tools:ignore="contentDescription" />
@ -56,12 +54,14 @@
android:text="@string/attendance_no_items"
android:textSize="20sp" />
</LinearLayout>
</FrameLayout>
</android.support.design.widget.CoordinatorLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="50dp"
android:layout_gravity="bottom"
android:background="@android:color/white"
android:elevation="10dp"
android:orientation="horizontal">
<Button
@ -72,7 +72,7 @@
android:layout_weight="1"
android:drawablePadding="4dp"
android:gravity="start|center"
android:text="@string/prev"
android:text="@string/all_prev"
android:textAlignment="gravity" />
<TextView
@ -91,9 +91,7 @@
android:layout_weight="1"
android:drawablePadding="4dp"
android:gravity="end|center"
android:text="@string/next"
android:text="@string/all_next"
android:textAlignment="gravity" />
</LinearLayout>
</LinearLayout>
</FrameLayout>

View File

@ -1,63 +0,0 @@
<android.support.design.widget.CoordinatorLayout 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:id="@+id/attendance_tab_fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="io.github.wulkanowy.ui.main.grades.GradesFragment">
<RelativeLayout
android:id="@+id/attendance_tab_fragment_progress_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:visibility="gone">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/attendance_tab_fragment_no_item_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<android.support.v7.widget.AppCompatImageView
android:id="@+id/attendance_tab_fragment_no_item_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/attendance_tab_fragment_no_item_text"
android:layout_centerHorizontal="true"
android:layout_marginTop="40dp"
android:minHeight="100dp"
android:minWidth="100dp"
app:tint="?android:attr/textColorPrimary"
app:srcCompat="@drawable/ic_menu_main_attendance_24dp"
tools:ignore="contentDescription" />
<TextView
android:id="@+id/attendance_tab_fragment_no_item_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="46dp"
android:gravity="center"
android:text="@string/attendance_no_items"
android:textSize="20sp" />
</RelativeLayout>
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/attendance_tab_fragment_swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/attendance_tab_fragment_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v4.widget.SwipeRefreshLayout>
</android.support.design.widget.CoordinatorLayout>

View File

@ -1,15 +1,13 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<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"
android:orientation="vertical">
android:layout_height="match_parent">
<FrameLayout
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical">
android:layout_height="match_parent"
android:layout_marginBottom="50dp">
<ProgressBar
android:id="@+id/examProgress"
@ -42,8 +40,8 @@
<android.support.v7.widget.AppCompatImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="100dp"
android:minWidth="100dp"
android:minHeight="100dp"
app:srcCompat="@drawable/ic_menu_main_exam_24dp"
app:tint="?android:attr/textColorPrimary"
tools:ignore="contentDescription" />
@ -56,12 +54,14 @@
android:text="@string/exam_no_items"
android:textSize="20sp" />
</LinearLayout>
</FrameLayout>
</android.support.design.widget.CoordinatorLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="50dp"
android:layout_gravity="bottom"
android:background="@android:color/white"
android:elevation="10dp"
android:orientation="horizontal">
<Button
@ -72,7 +72,7 @@
android:layout_weight="1"
android:drawablePadding="4dp"
android:gravity="start|center"
android:text="@string/prev"
android:text="@string/all_prev"
android:textAlignment="gravity" />
<TextView
@ -91,9 +91,7 @@
android:layout_weight="1"
android:drawablePadding="4dp"
android:gravity="end|center"
android:text="@string/next"
android:text="@string/all_next"
android:textAlignment="gravity" />
</LinearLayout>
</LinearLayout>
</FrameLayout>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -7,7 +7,7 @@
<android.support.design.widget.TabLayout
android:id="@+id/gradeTabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="48dp"
android:background="@color/colorPrimary"
android:elevation="5dp"
android:visibility="invisible"
@ -21,13 +21,13 @@
android:id="@+id/gradeViewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/gradeTabLayout"
android:layout_marginTop="48dp"
android:visibility="invisible" />
<ProgressBar
android:id="@+id/gradeProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_gravity="center"
android:indeterminate="true" />
</RelativeLayout>
</android.support.design.widget.CoordinatorLayout>

View File

@ -1,15 +1,13 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<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"
android:orientation="vertical">
android:layout_height="match_parent">
<FrameLayout
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical">
android:layout_height="match_parent"
android:layout_marginBottom="50dp">
<ProgressBar
android:id="@+id/timetableProgress"
@ -42,8 +40,8 @@
<android.support.v7.widget.AppCompatImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="100dp"
android:minWidth="100dp"
android:minHeight="100dp"
app:srcCompat="@drawable/ic_menu_main_timetable_24dp"
app:tint="?android:attr/textColorPrimary"
tools:ignore="contentDescription" />
@ -56,12 +54,14 @@
android:text="@string/timetable_no_items"
android:textSize="20sp" />
</LinearLayout>
</FrameLayout>
</android.support.design.widget.CoordinatorLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="50dp"
android:layout_gravity="bottom"
android:background="@android:color/white"
android:elevation="10dp"
android:orientation="horizontal">
<Button
@ -72,7 +72,7 @@
android:layout_weight="1"
android:drawablePadding="4dp"
android:gravity="start|center"
android:text="@string/prev"
android:text="@string/all_prev"
android:textAlignment="gravity" />
<TextView
@ -91,9 +91,7 @@
android:layout_weight="1"
android:drawablePadding="4dp"
android:gravity="end|center"
android:text="@string/next"
android:text="@string/all_next"
android:textAlignment="gravity" />
</LinearLayout>
</LinearLayout>
</FrameLayout>

View File

@ -1,86 +0,0 @@
<FrameLayout 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:foreground="?attr/selectableItemBackgroundBorderless">
<RelativeLayout 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"
android:paddingBottom="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp">
<TextView
android:id="@+id/attendance_header_day"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginEnd="15dp"
android:layout_marginRight="15dp"
android:layout_toLeftOf="@id/attendance_header_alert_image"
android:layout_toStartOf="@+id/attendance_header_alert_image"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/app_name"
android:textSize="19sp" />
<TextView
android:id="@+id/attendance_header_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/attendance_header_day"
android:layout_marginTop="5dp"
android:maxLines="1"
android:text="@string/app_name"
android:textColor="?android:attr/android:textColorSecondary"
android:textSize="14sp" />
<TextView
android:id="@+id/attendance_header_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@+id/attendance_header_date"
android:layout_toRightOf="@+id/attendance_header_date"
android:layout_below="@+id/attendance_header_day"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:layout_marginTop="5dp"
android:maxLines="1"
android:text="@string/app_name"
android:textColor="?android:attr/android:textColorSecondary"
android:textSize="14sp" />
<TextView
android:id="@+id/attendance_header_free_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginLeft="180dp"
android:layout_marginStart="180dp"
android:gravity="end"
android:maxLines="2"
android:text="@string/attendance_no_items"
android:textColor="?android:attr/android:textColorSecondary"
android:textSize="16sp" />
<ImageView
android:id="@+id/attendance_header_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"
tools:ignore="contentDescription" />
</RelativeLayout>
</FrameLayout>

View File

@ -1,19 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorControlHighlight"
android:minHeight="40dp"
android:paddingBottom="10dp"
android:orientation="horizontal"
android:paddingLeft="20dp"
android:paddingTop="10dp"
android:paddingRight="20dp"
android:paddingTop="10dp">
android:paddingBottom="10dp">
<TextView
android:id="@+id/examHeaderDay"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_weight="1"
android:text="@string/app_name"
android:textSize="18sp" />
@ -21,15 +22,10 @@
android:id="@+id/examHeaderDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:layout_toEndOf="@id/examHeaderDay"
android:layout_toRightOf="@id/examHeaderDay"
android:layout_marginLeft="10dp"
android:gravity="end"
android:text="@string/app_name"
android:textSize="13sp" />
</RelativeLayout>
</LinearLayout>

View File

@ -1,22 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorControlHighlight"
android:paddingBottom="7dp"
android:orientation="horizontal"
android:paddingLeft="20dp"
android:paddingTop="7dp"
android:paddingRight="20dp"
android:paddingTop="7dp">
android:paddingBottom="7dp">
<TextView
android:id="@+id/gradeSummaryHeaderName"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginEnd="40dp"
android:layout_marginRight="40dp"
android:layout_toLeftOf="@id/gradeSummaryHeaderAverage"
android:layout_toStartOf="@id/gradeSummaryHeaderAverage"
android:layout_weight="1"
android:text="@string/app_name"
android:textSize="17sp" />
@ -24,11 +23,8 @@
android:id="@+id/gradeSummaryHeaderAverage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:gravity="end"
android:text="@string/app_name"
android:textSize="12sp" />
</RelativeLayout>
</LinearLayout>

View File

@ -1,71 +0,0 @@
<FrameLayout 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:foreground="?attr/selectableItemBackgroundBorderless">
<RelativeLayout 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"
android:paddingBottom="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp">
<TextView
android:id="@+id/timetable_header_day"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginEnd="15dp"
android:layout_marginRight="15dp"
android:layout_toLeftOf="@id/timetable_header_alert_image"
android:layout_toStartOf="@+id/timetable_header_alert_image"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/app_name"
android:textSize="19sp" />
<TextView
android:id="@+id/timetable_header_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/timetable_header_day"
android:layout_marginTop="5dp"
android:maxLines="1"
android:text="@string/app_name"
android:textColor="?android:attr/android:textColorSecondary"
android:textSize="14sp" />
<TextView
android:id="@+id/timetable_header_free_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginLeft="180dp"
android:layout_marginStart="180dp"
android:gravity="end"
android:maxLines="2"
android:text="@string/app_name"
android:textColor="?android:attr/android:textColorSecondary"
android:textSize="16sp" />
<ImageView
android:id="@+id/timetable_header_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"
tools:ignore="contentDescription" />
</RelativeLayout>
</FrameLayout>

View File

@ -4,6 +4,7 @@
android:id="@+id/attendanceItemContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/ic_all_divider"
android:foreground="?attr/selectableItemBackgroundBorderless"
android:paddingStart="12dp"
android:paddingLeft="12dp"

View File

@ -2,14 +2,15 @@
android:id="@+id/exams_subitem_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/ic_all_divider"
android:foreground="?attr/selectableItemBackgroundBorderless">
<TextView
android:id="@+id/examItemSubject"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginStart="20dp"
android:layout_marginLeft="20dp"
android:layout_marginTop="10dp"
android:text="@string/app_name"
android:textSize="15sp" />
@ -18,11 +19,11 @@
android:id="@+id/examItemType"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/examItemSubject"
android:layout_alignStart="@id/examItemSubject"
android:layout_below="@id/examItemSubject"
android:layout_marginBottom="5dp"
android:layout_alignStart="@id/examItemSubject"
android:layout_alignLeft="@id/examItemSubject"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:text="@string/app_name"
android:textSize="13sp" />
@ -30,15 +31,15 @@
android:id="@+id/examItemTeacher"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/examItemSubject"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_below="@id/examItemSubject"
android:layout_marginBottom="10dp"
android:layout_marginEnd="20dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="20dp"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
android:layout_marginBottom="10dp"
android:layout_toEndOf="@id/examItemType"
android:layout_toRightOf="@id/examItemType"
android:gravity="end"

View File

@ -1,16 +1,18 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<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:minHeight="35dp"
android:orientation="horizontal">
<TextView
android:id="@+id/gradeSummaryItemTitle"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="20dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="20dp"
android:layout_marginLeft="20dp"
android:layout_weight="1"
android:text="@string/grade_summary_predicted_grade"
android:textSize="14sp" />
@ -18,16 +20,12 @@
android:id="@+id/gradeSummaryItemGrade"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginEnd="25dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="25dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
android:layout_toEndOf="@id/gradeSummaryItemTitle"
android:layout_toRightOf="@id/gradeSummaryItemTitle"
android:layout_marginLeft="10dp"
android:layout_marginEnd="25dp"
android:layout_marginRight="25dp"
android:gravity="end"
android:text="@string/app_name"
android:textSize="12sp" />
</RelativeLayout>
</LinearLayout>

View File

@ -4,13 +4,14 @@
android:id="@+id/timetable_subitem_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/ic_all_divider"
android:foreground="?attr/selectableItemBackgroundBorderless"
android:paddingBottom="7dp"
android:paddingEnd="12dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingStart="12dp"
android:paddingTop="7dp">
android:paddingLeft="12dp"
android:paddingTop="7dp"
android:paddingEnd="12dp"
android:paddingRight="12dp"
android:paddingBottom="7dp">
<TextView
android:id="@+id/timetableItemNumber"
@ -28,10 +29,10 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginEnd="40dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="40dp"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginEnd="40dp"
android:layout_marginRight="40dp"
android:layout_toEndOf="@+id/timetableItemNumber"
android:layout_toRightOf="@+id/timetableItemNumber"
android:ellipsize="end"
@ -44,9 +45,9 @@
android:id="@+id/timetableItemTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/timetableItemNumber"
android:layout_alignLeft="@id/timetableItemSubject"
android:layout_alignStart="@id/timetableItemSubject"
android:layout_alignLeft="@id/timetableItemSubject"
android:layout_alignBottom="@+id/timetableItemNumber"
android:maxLines="1"
android:text="@string/app_name"
android:textColor="?android:attr/android:textColorSecondary"
@ -57,10 +58,10 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/timetableItemNumber"
android:layout_marginEnd="40dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="40dp"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginEnd="40dp"
android:layout_marginRight="40dp"
android:layout_toEndOf="@+id/timetableItemTime"
android:layout_toRightOf="@+id/timetableItemTime"
android:maxLines="1"

View File

@ -104,6 +104,8 @@
<string name="all_cancel">Anuluj</string>
<string name="all_no_data">Brak danych</string>
<string name="all_subject">Przedmiot</string>
<string name="all_prev">Poprzedni</string>
<string name="all_next">Następny</string>
<!--Timetable Widget-->
@ -157,6 +159,7 @@
<string name="all_red">Czerwony</string>
<string name="all_blue">Niebieski</string>
<string name="all_green">Zielony</string>
<string name="all_purple">Fioletowy</string>
<string name="all_empty_color">Brak koloru</string>
@ -166,7 +169,4 @@
<string name="all_sync_fail">Podczas synchronizacji wystąpił błąd</string>
<string name="all_timeout">Zbyt długie oczekiwanie na połączenie</string>
<string name="all_login_failed">Logowanie nie powiodło się. Spróbuj ponownie lub zrestartuj aplikację</string>
<string name="prev">Poprzedni</string>
<string name="next">Następny</string>
</resources>

View File

@ -59,7 +59,7 @@
<!--Timetable-->
<string name="timetable_lesson">Lesson</string>
<string name="timetable_room">Room %s</string>
<string name="timetable_room">Room</string>
<string name="timetable_group">Group</string>
<string name="timetable_time">Hours</string>
<string name="timetable_changes">Changes</string>
@ -99,6 +99,8 @@
<string name="all_cancel">Cancel</string>
<string name="all_no_data">No data</string>
<string name="all_subject">Subject</string>
<string name="all_prev">Prev</string>
<string name="all_next">Next</string>
<!--Timetable Widget-->
@ -150,6 +152,7 @@
<string name="all_red">Red</string>
<string name="all_blue">Blue</string>
<string name="all_green">Green</string>
<string name="all_purple">Purple</string>
<string name="all_empty_color">No color</string>
@ -159,6 +162,4 @@
<string name="all_sync_fail">There was an error during synchronization</string>
<string name="all_timeout">Too long wait for connection</string>
<string name="all_login_failed">Login is failed. Try again or restart the app</string>
<string name="prev">Prev</string>
<string name="next">Next</string>
</resources>

View File

@ -11,7 +11,6 @@ import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDateTime
import java.sql.Date
class TimetableRemoteTest {
@ -41,7 +40,7 @@ class TimetableRemoteTest {
every { semesterMock.studentId } returns "1"
every { semesterMock.diaryId } returns "1"
val timetable = TimetableRemote(mockApi).getLessons(semesterMock,
val timetable = TimetableRemote(mockApi).getTimetable(semesterMock,
LocalDate.of(2018, 9, 10),
LocalDate.of(2018, 9, 15)
).blockingGet()

View File

@ -22,7 +22,7 @@ class LoginPresenterTest {
MockitoAnnotations.initMocks(this)
clearInvocations(loginView)
presenter = LoginPresenter(errorHandler)
presenter.attachView(loginView)
presenter.onAttachView(loginView)
}
@Test

View File

@ -31,7 +31,7 @@ class LoginFormPresenterTest {
MockitoAnnotations.initMocks(this)
clearInvocations(repository, loginFormView)
presenter = LoginFormPresenter(TestSchedulers(), errorHandler, repository)
presenter.attachView(loginFormView)
presenter.onAttachView(loginFormView)
}
@Test

View File

@ -35,7 +35,7 @@ class LoginOptionsPresenterTest {
MockitoAnnotations.initMocks(this)
clearInvocations(repository, loginOptionsView)
presenter = LoginOptionsPresenter(errorHandler, repository, TestSchedulers())
presenter.attachView(loginOptionsView)
presenter.onAttachView(loginOptionsView)
}
@Test

View File

@ -23,7 +23,7 @@ class MainPresenterTest {
clearInvocations(mainView)
presenter = MainPresenter(errorHandler)
presenter.attachView(mainView)
presenter.onAttachView(mainView)
}
@Test

View File

@ -31,14 +31,14 @@ class SplashPresenterTest {
@Test
fun testOpenLoginView() {
doReturn(false).`when`(sessionRepository).isSessionSaved
presenter.attachView(splashView)
presenter.onAttachView(splashView)
verify(splashView).openLoginView()
}
@Test
fun testMainMainView() {
doReturn(true).`when`(sessionRepository).isSessionSaved
presenter.attachView(splashView)
presenter.onAttachView(splashView)
verify(splashView).openMainView()
}
}

View File

@ -34,14 +34,10 @@ class GradeExtensionTest {
@Test
fun calcSummaryAverage() {
assertEquals(2.5, listOf(
GradeSummary(0, "", "", "", "",
"5"),
GradeSummary(0, "", "", "", "",
"-5"),
GradeSummary(0, "", "", "", "",
"test"),
GradeSummary(0, "", "", "", "",
"0")
GradeSummary("", "", "", "", "5"),
GradeSummary("", "", "", "", "-5"),
GradeSummary("", "", "", "", "test"),
GradeSummary("", "", "", "", "0")
).calcAverage(), 0.005)
}
}

View File

@ -14,33 +14,33 @@ class TimeExtensionTest {
}
@Test
fun toFormattedStringTest() {
assertEquals("2018-10-01", LocalDate.of(2018, 10, 1).toFormattedString())
fun toFormattedStringLocalDateTest() {
assertEquals("01.10.2018", LocalDate.of(2018, 10, 1).toFormattedString())
assertEquals("2018-10.01", LocalDate.of(2018, 10, 1).toFormattedString("yyyy-MM.dd"))
}
@Test
fun toFormat_LocalDateTime() {
assertEquals("2018-10-01", LocalDateTime.of(2018, 10, 1, 10, 0, 0).toFormattedString())
fun toFormattedStringLocalDateTimeTest() {
assertEquals("01.10.2018", LocalDateTime.of(2018, 10, 1, 10, 0, 0).toFormattedString())
assertEquals("2018-10-01 10:00:00", LocalDateTime.of(2018, 10, 1, 10, 0, 0).toFormattedString("uuuu-MM-dd HH:mm:ss"))
}
@Test
fun weekFirstDayAlwaysCurrentTest() {
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 10, 2).weekFirstDayAlwaysCurrent)
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 10, 5).weekFirstDayAlwaysCurrent)
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 10, 6).weekFirstDayAlwaysCurrent)
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 10, 7).weekFirstDayAlwaysCurrent)
assertEquals(LocalDate.of(2018, 10, 8), LocalDate.of(2018, 10, 8).weekFirstDayAlwaysCurrent)
fun mondayTest() {
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 10, 2).monday)
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 10, 5).monday)
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 10, 6).monday)
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 10, 7).monday)
assertEquals(LocalDate.of(2018, 10, 8), LocalDate.of(2018, 10, 8).monday)
}
@Test
fun weekFirstDayNextOnWeekEndTest() {
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 10, 2).weekFirstDayNextOnWeekEnd)
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 10, 5).weekFirstDayNextOnWeekEnd)
assertEquals(LocalDate.of(2018, 10, 8), LocalDate.of(2018, 10, 6).weekFirstDayNextOnWeekEnd)
assertEquals(LocalDate.of(2018, 10, 8), LocalDate.of(2018, 10, 7).weekFirstDayNextOnWeekEnd)
assertEquals(LocalDate.of(2018, 10, 8), LocalDate.of(2018, 10, 8).weekFirstDayNextOnWeekEnd)
fun fridayTest() {
assertEquals(LocalDate.of(2018, 10, 5), LocalDate.of(2018, 10, 2).friday)
assertEquals(LocalDate.of(2018, 10, 5), LocalDate.of(2018, 10, 5).friday)
assertEquals(LocalDate.of(2018, 10, 5), LocalDate.of(2018, 10, 6).friday)
assertEquals(LocalDate.of(2018, 10, 5), LocalDate.of(2018, 10, 7).friday)
assertEquals(LocalDate.of(2018, 10, 12), LocalDate.of(2018, 10, 8).friday)
}
@Test
@ -53,43 +53,44 @@ class TimeExtensionTest {
@Test
fun nextSchoolDayTest() {
assertEquals(LocalDate.of(2018, 10, 2), LocalDate.of(2018, 10, 1).nextWorkDay)
assertEquals(LocalDate.of(2018, 10, 3), LocalDate.of(2018, 10, 2).nextWorkDay)
assertEquals(LocalDate.of(2018, 10, 4), LocalDate.of(2018, 10, 3).nextWorkDay)
assertEquals(LocalDate.of(2018, 10, 5), LocalDate.of(2018, 10, 4).nextWorkDay)
assertEquals(LocalDate.of(2018, 10, 8), LocalDate.of(2018, 10, 5).nextWorkDay)
assertEquals(LocalDate.of(2018, 10, 8), LocalDate.of(2018, 10, 6).nextWorkDay)
assertEquals(LocalDate.of(2018, 10, 8), LocalDate.of(2018, 10, 7).nextWorkDay)
assertEquals(LocalDate.of(2018, 10, 9), LocalDate.of(2018, 10, 8).nextWorkDay)
assertEquals(LocalDate.of(2018, 10, 2), LocalDate.of(2018, 10, 1).nextSchoolDay)
assertEquals(LocalDate.of(2018, 10, 3), LocalDate.of(2018, 10, 2).nextSchoolDay)
assertEquals(LocalDate.of(2018, 10, 4), LocalDate.of(2018, 10, 3).nextSchoolDay)
assertEquals(LocalDate.of(2018, 10, 5), LocalDate.of(2018, 10, 4).nextSchoolDay)
assertEquals(LocalDate.of(2018, 10, 8), LocalDate.of(2018, 10, 5).nextSchoolDay)
assertEquals(LocalDate.of(2018, 10, 8), LocalDate.of(2018, 10, 6).nextSchoolDay)
assertEquals(LocalDate.of(2018, 10, 8), LocalDate.of(2018, 10, 7).nextSchoolDay)
assertEquals(LocalDate.of(2018, 10, 9), LocalDate.of(2018, 10, 8).nextSchoolDay)
}
@Test
fun previousSchoolDayTest() {
assertEquals(LocalDate.of(2018, 10, 9), LocalDate.of(2018, 10, 10).previousWorkDay)
assertEquals(LocalDate.of(2018, 10, 8), LocalDate.of(2018, 10, 9).previousWorkDay)
assertEquals(LocalDate.of(2018, 10, 5), LocalDate.of(2018, 10, 8).previousWorkDay)
assertEquals(LocalDate.of(2018, 10, 5), LocalDate.of(2018, 10, 7).previousWorkDay)
assertEquals(LocalDate.of(2018, 10, 5), LocalDate.of(2018, 10, 6).previousWorkDay)
assertEquals(LocalDate.of(2018, 10, 4), LocalDate.of(2018, 10, 5).previousWorkDay)
assertEquals(LocalDate.of(2018, 10, 3), LocalDate.of(2018, 10, 4).previousWorkDay)
assertEquals(LocalDate.of(2018, 10, 9), LocalDate.of(2018, 10, 10).previousSchoolDay)
assertEquals(LocalDate.of(2018, 10, 8), LocalDate.of(2018, 10, 9).previousSchoolDay)
assertEquals(LocalDate.of(2018, 10, 5), LocalDate.of(2018, 10, 8).previousSchoolDay)
assertEquals(LocalDate.of(2018, 10, 5), LocalDate.of(2018, 10, 7).previousSchoolDay)
assertEquals(LocalDate.of(2018, 10, 5), LocalDate.of(2018, 10, 6).previousSchoolDay)
assertEquals(LocalDate.of(2018, 10, 4), LocalDate.of(2018, 10, 5).previousSchoolDay)
assertEquals(LocalDate.of(2018, 10, 3), LocalDate.of(2018, 10, 4).previousSchoolDay)
}
@Test
fun nextOrSameSchoolDayTest() {
assertEquals(LocalDate.of(2018, 9, 28), LocalDate.of(2018, 9, 28).nextOrSameSchoolDay)
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 9, 29).nextOrSameSchoolDay)
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 9, 30).nextOrSameSchoolDay)
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 10, 1).nextOrSameSchoolDay)
assertEquals(LocalDate.of(2018, 10, 2), LocalDate.of(2018, 10, 2).nextOrSameSchoolDay)
}
@Test
fun nearSchoolDayPrevOnWeekEndTest() {
assertEquals(LocalDate.of(2018, 9, 28), LocalDate.of(2018, 9, 28).nearSchoolDayPrevOnWeekEnd)
assertEquals(LocalDate.of(2018, 9, 28), LocalDate.of(2018, 9, 29).nearSchoolDayPrevOnWeekEnd)
assertEquals(LocalDate.of(2018, 9, 28), LocalDate.of(2018, 9, 30).nearSchoolDayPrevOnWeekEnd)
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 10, 1).nearSchoolDayPrevOnWeekEnd)
assertEquals(LocalDate.of(2018, 10, 2), LocalDate.of(2018, 10, 2).nearSchoolDayPrevOnWeekEnd)
}
@Test
fun nearSchoolDayNextOnWeekEndTest() {
assertEquals(LocalDate.of(2018, 9, 28), LocalDate.of(2018, 9, 28).nearSchoolDayNextOnWeekEnd)
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 9, 29).nearSchoolDayNextOnWeekEnd)
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 9, 30).nearSchoolDayNextOnWeekEnd)
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 10, 1).nearSchoolDayNextOnWeekEnd)
assertEquals(LocalDate.of(2018, 10, 2), LocalDate.of(2018, 10, 2).nearSchoolDayNextOnWeekEnd)
fun previousOrSameSchoolDayTest() {
assertEquals(LocalDate.of(2018, 9, 28), LocalDate.of(2018, 9, 28).previousOrSameSchoolDay)
assertEquals(LocalDate.of(2018, 9, 28), LocalDate.of(2018, 9, 29).previousOrSameSchoolDay)
assertEquals(LocalDate.of(2018, 9, 28), LocalDate.of(2018, 9, 30).previousOrSameSchoolDay)
assertEquals(LocalDate.of(2018, 10, 1), LocalDate.of(2018, 10, 1).previousOrSameSchoolDay)
assertEquals(LocalDate.of(2018, 10, 2), LocalDate.of(2018, 10, 2).previousOrSameSchoolDay)
}
@Test