Refactor exam module (#157)

This commit is contained in:
Mikołaj Pich 2018-09-24 15:21:47 +02:00 committed by Rafał Borcz
parent b617957891
commit a1f64baca4
51 changed files with 1220 additions and 480 deletions

View File

@ -8,8 +8,8 @@ apply from: 'sonarqube.gradle'
apply plugin: 'com.github.triplet.play' apply plugin: 'com.github.triplet.play'
android { android {
compileSdkVersion 27 compileSdkVersion 28
buildToolsVersion '27.0.3' buildToolsVersion '28.0.2'
playAccountConfigs { playAccountConfigs {
defaultAccountConfig { defaultAccountConfig {
@ -22,7 +22,7 @@ android {
applicationId "io.github.wulkanowy" applicationId "io.github.wulkanowy"
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 15 minSdkVersion 15
targetSdkVersion 27 targetSdkVersion 28
versionCode 16 versionCode 16
versionName "0.5.2" versionName "0.5.2"
multiDexEnabled true multiDexEnabled true
@ -68,11 +68,11 @@ play {
uploadImages = true uploadImages = true
} }
ext.supportVersion = "27.1.1" ext.supportVersion = "28.0.0-rc02"
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'com.github.wulkanowy:api:88ede83149' implementation 'com.github.wulkanowy:api:ad57669'
implementation "com.android.support:support-v4:$supportVersion" implementation "com.android.support:support-v4:$supportVersion"
implementation "com.android.support:design:$supportVersion" implementation "com.android.support:design:$supportVersion"
@ -98,7 +98,7 @@ dependencies {
implementation 'com.github.pwittchen:reactivenetwork-rx2:2.1.0' implementation 'com.github.pwittchen:reactivenetwork-rx2:2.1.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
implementation "io.reactivex.rxjava2:rxjava:2.2.0" implementation "io.reactivex.rxjava2:rxjava:2.2.1"
implementation "org.apache.commons:commons-lang3:3.8" implementation "org.apache.commons:commons-lang3:3.8"
implementation "org.apache.commons:commons-collections4:4.2" implementation "org.apache.commons:commons-collections4:4.2"

View File

@ -0,0 +1,50 @@
package io.github.wulkanowy.data.repositories.local
import android.arch.persistence.room.Room
import android.support.test.InstrumentationRegistry
import android.support.test.runner.AndroidJUnit4
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Semester
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.threeten.bp.LocalDate
import java.sql.Date
import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
class ExamLocalTest {
private lateinit var examLocal: ExamLocal
private lateinit var testDb: AppDatabase
@Before
fun createDb() {
testDb = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(), AppDatabase::class.java).build()
examLocal = ExamLocal(testDb.examsDao())
}
@After
fun closeDb() {
testDb.close()
}
@Test
fun saveAndReadTest() {
examLocal.saveExams(listOf(
Exam(studentId = "1", diaryId = "2", date = Date.valueOf("2018-09-10")),
Exam(studentId = "1", diaryId = "2", date = Date.valueOf("2018-09-14")),
Exam(studentId = "1", diaryId = "2", date = Date.valueOf("2018-09-17")) // in next week
))
val exams = examLocal
.getExams(Semester(studentId = "1", diaryId = "2", semesterId = "3"), LocalDate.of(2018, 9, 10))
.blockingGet()
assertEquals(2, exams.size)
assertEquals(exams[0].date, Date.valueOf("2018-09-10"))
assertEquals(exams[1].date, Date.valueOf("2018-09-14"))
}
}

View File

@ -14,9 +14,9 @@ import org.junit.runner.RunWith
import kotlin.test.assertEquals import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class StudentLocalTest { class SessionLocalTest {
private lateinit var studentLocal: StudentLocal private lateinit var studentLocal: SessionLocal
private lateinit var testDb: AppDatabase private lateinit var testDb: AppDatabase
@ -28,7 +28,7 @@ class StudentLocalTest {
testDb = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java) testDb = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java)
.build() .build()
sharedHelper = SharedPrefHelper(context.getSharedPreferences("TEST", Context.MODE_PRIVATE)) sharedHelper = SharedPrefHelper(context.getSharedPreferences("TEST", Context.MODE_PRIVATE))
studentLocal = StudentLocal(testDb.studentDao(), sharedHelper, context) studentLocal = SessionLocal(testDb.studentDao(), testDb.semesterDao(), sharedHelper, context)
} }
@After @After
@ -38,12 +38,12 @@ class StudentLocalTest {
@Test @Test
fun saveAndReadTest() { fun saveAndReadTest() {
studentLocal.save(Student(email = "test", password = "test123", schoolId = "23")).blockingAwait() studentLocal.saveStudent(Student(email = "test", password = "test123", schoolId = "23")).blockingAwait()
assert(sharedHelper.getLong(StudentLocal.CURRENT_USER_KEY, 0) == 1L) assert(sharedHelper.getLong(SessionLocal.LAST_USER_KEY, 0) == 1L)
assert(studentLocal.isStudentLoggedIn) assert(studentLocal.isSessionSaved)
val student = studentLocal.getCurrentStudent().blockingGet() val student = studentLocal.getLastStudent().blockingGet()
assertEquals("23", student.schoolId) assertEquals("23", student.schoolId)
} }
} }

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="io.github.wulkanowy" package="io.github.wulkanowy"
android:installLocation="internalOnly"> android:installLocation="internalOnly">
@ -13,7 +14,9 @@
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/WulkanowyTheme"> android:theme="@style/WulkanowyTheme"
android:usesCleartextTraffic="true"
tools:targetApi="m">
<activity <activity
android:name=".ui.splash.SplashActivity" android:name=".ui.splash.SplashActivity"
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"

View File

@ -32,6 +32,9 @@ internal class RepositoryModule {
.build() .build()
} }
@Provides
fun provideErrorHandler(context: Context) = ErrorHandler(context.resources)
@Singleton @Singleton
@Provides @Provides
fun provideSharedPref(context: Context): SharedPreferences { fun provideSharedPref(context: Context): SharedPreferences {
@ -41,4 +44,12 @@ internal class RepositoryModule {
@Singleton @Singleton
@Provides @Provides
fun provideStudentDao(database: AppDatabase) = database.studentDao() fun provideStudentDao(database: AppDatabase) = database.studentDao()
@Singleton
@Provides
fun provideSemesterDao(database: AppDatabase) = database.semesterDao()
@Singleton
@Provides
fun provideExamDao(database: AppDatabase) = database.examsDao()
} }

View File

@ -2,21 +2,31 @@ package io.github.wulkanowy.data.db
import android.arch.persistence.room.Database import android.arch.persistence.room.Database
import android.arch.persistence.room.RoomDatabase import android.arch.persistence.room.RoomDatabase
import android.arch.persistence.room.TypeConverters
import io.github.wulkanowy.data.db.dao.ExamDao
import io.github.wulkanowy.data.db.dao.SemesterDao import io.github.wulkanowy.data.db.dao.SemesterDao
import io.github.wulkanowy.data.db.dao.StudentDao import io.github.wulkanowy.data.db.dao.StudentDao
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
@Database( @Database(
entities = [Student::class, Semester::class], entities = [
Student::class,
Semester::class,
Exam::class
],
version = 1, version = 1,
exportSchema = false exportSchema = false
) )
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
abstract fun studentDao(): StudentDao abstract fun studentDao(): StudentDao
abstract fun semesterDao(): SemesterDao abstract fun semesterDao(): SemesterDao
abstract fun examsDao(): ExamDao
} }

View File

@ -0,0 +1,14 @@
package io.github.wulkanowy.data.db
import android.arch.persistence.room.TypeConverter
import java.util.*
class Converters {
@TypeConverter
fun fromTimestamp(value: Long?): Date? = value?.run { Date(value) }
@TypeConverter
fun dateToTimestamp(date: Date?): Long? = date?.time
}

View File

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

View File

@ -3,11 +3,16 @@ package io.github.wulkanowy.data.db.dao
import android.arch.persistence.room.Dao import android.arch.persistence.room.Dao
import android.arch.persistence.room.Insert import android.arch.persistence.room.Insert
import android.arch.persistence.room.OnConflictStrategy.REPLACE import android.arch.persistence.room.OnConflictStrategy.REPLACE
import android.arch.persistence.room.Query
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.reactivex.Single
@Dao @Dao
interface SemesterDao { interface SemesterDao {
@Insert(onConflict = REPLACE) @Insert(onConflict = REPLACE)
fun insert(semester: Semester): Long fun insertAll(semester: List<Semester>)
@Query("SELECT * FROM Semesters WHERE student_id = :studentId")
fun getSemester(studentId: String): Single<List<Semester>>
} }

View File

@ -5,7 +5,7 @@ import android.arch.persistence.room.Insert
import android.arch.persistence.room.OnConflictStrategy.REPLACE import android.arch.persistence.room.OnConflictStrategy.REPLACE
import android.arch.persistence.room.Query import android.arch.persistence.room.Query
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.reactivex.Single import io.reactivex.Maybe
@Dao @Dao
interface StudentDao { interface StudentDao {
@ -14,5 +14,5 @@ interface StudentDao {
fun insert(student: Student): Long fun insert(student: Student): Long
@Query("SELECT * FROM Students WHERE id = :id") @Query("SELECT * FROM Students WHERE id = :id")
fun load(id: Long): Single<Student> fun load(id: Long): Maybe<Student>
} }

View File

@ -0,0 +1,38 @@
package io.github.wulkanowy.data.db.entities
import android.arch.persistence.room.ColumnInfo
import android.arch.persistence.room.Entity
import android.arch.persistence.room.PrimaryKey
import java.io.Serializable
import java.util.*
@Entity(tableName = "Exams")
data class Exam(
@PrimaryKey(autoGenerate = true)
var id: Long = 0,
@ColumnInfo(name = "student_id")
var studentId: String = "",
@ColumnInfo(name = "diary_id")
var diaryId: String = "",
var date: Date,
@ColumnInfo(name = "entry_date")
var entryDate: Date = Date(),
var subject: String = "",
var group: String = "",
var type: String = "",
var description: String = "",
var teacher: String = "",
@ColumnInfo(name = "teacher_symbol")
var teacherSymbol: String = ""
) : Serializable

View File

@ -6,12 +6,15 @@ import android.arch.persistence.room.Index
import android.arch.persistence.room.PrimaryKey import android.arch.persistence.room.PrimaryKey
@Entity(tableName = "Semesters", @Entity(tableName = "Semesters",
indices = [Index(value = ["diary_id", "semester_id"], unique = true)]) indices = [Index(value = ["semester_id", "diary_id", "student_id"], unique = true)])
data class Semester( data class Semester(
@PrimaryKey @PrimaryKey(autoGenerate = true)
var id: Long = 0, var id: Long = 0,
@ColumnInfo(name = "student_id")
var studentId: String,
@ColumnInfo(name = "diary_id") @ColumnInfo(name = "diary_id")
var diaryId: String, var diaryId: String,
@ -22,5 +25,8 @@ data class Semester(
var semesterId: String, var semesterId: String,
@ColumnInfo(name = "semester_name") @ColumnInfo(name = "semester_name")
var semesterName: String = "" var semesterName: Int = 0,
@ColumnInfo(name = "is_current")
var current: Boolean = false
) )

View File

@ -0,0 +1,39 @@
package io.github.wulkanowy.data.repositories
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import io.github.wulkanowy.data.db.entities.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.reactivex.Single
import org.threeten.bp.LocalDate
import java.net.UnknownHostException
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ExamRepository @Inject constructor(
private val settings: InternetObservingSettings,
private val local: ExamLocal,
private val remote: ExamRemote
) {
fun getExams(semester: Semester, date: LocalDate, forceRefresh: Boolean = false): Single<List<Exam>> {
return local.getExams(semester, date).filter { !forceRefresh }
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap {
if (it) remote.getExams(semester, date)
else Single.error(UnknownHostException())
}.flatMap { newExams ->
local.getExams(semester, date).toSingle(emptyList())
.map {
local.deleteExams(it - newExams)
local.saveExams(newExams - it)
newExams
}
}
)
}
}

View File

@ -2,9 +2,10 @@ package io.github.wulkanowy.data.repositories
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.local.StudentLocal import io.github.wulkanowy.data.repositories.local.SessionLocal
import io.github.wulkanowy.data.repositories.remote.StudentRemote import io.github.wulkanowy.data.repositories.remote.SessionRemote
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Single import io.reactivex.Single
import java.net.UnknownHostException import java.net.UnknownHostException
@ -12,17 +13,17 @@ import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class StudentRepository @Inject constructor( class SessionRepository @Inject constructor(
private val local: StudentLocal, private val local: SessionLocal,
private val remote: StudentRemote, private val remote: SessionRemote,
private val settings: InternetObservingSettings) { private val settings: InternetObservingSettings) {
val isSessionSaved
get() = local.isSessionSaved
lateinit var cachedStudents: Single<List<Student>> lateinit var cachedStudents: Single<List<Student>>
private set private set
val isStudentLoggedIn: Boolean
get() = local.isStudentLoggedIn
fun getConnectedStudents(email: String, password: String, symbol: String): Single<List<Student>> { fun getConnectedStudents(email: String, password: String, symbol: String): Single<List<Student>> {
cachedStudents = ReactiveNetwork.checkInternetConnectivity(settings) cachedStudents = ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap { isConnected -> .flatMap { isConnected ->
@ -32,9 +33,19 @@ class StudentRepository @Inject constructor(
return cachedStudents return cachedStudents
} }
fun save(student: Student): Completable = local.save(student) fun getSemesters(): Single<List<Semester>> {
return local.getLastStudent()
.flatMapSingle {
remote.initApi(it, true)
local.getSemesters(it)
}
}
fun getCurrentStudent(): Single<Student> = local.getCurrentStudent() fun saveStudent(student: Student): Completable {
return remote.getSemesters(student).flatMapCompletable {
local.saveSemesters(it)
}.concatWith(local.saveStudent(student))
}
fun clearCache() { fun clearCache() {
cachedStudents = Single.just(emptyList()) cachedStudents = Single.just(emptyList())

View File

@ -0,0 +1,28 @@
package io.github.wulkanowy.data.repositories.local
import io.github.wulkanowy.data.db.dao.ExamDao
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.utils.extension.toDate
import io.reactivex.Maybe
import org.threeten.bp.DayOfWeek
import org.threeten.bp.LocalDate
import org.threeten.bp.temporal.TemporalAdjusters
import javax.inject.Inject
class ExamLocal @Inject constructor(private val examDb: ExamDao) {
fun getExams(semester: Semester, startDate: LocalDate): Maybe<List<Exam>> {
return examDb.getExams(semester.diaryId, semester.studentId, startDate.toDate(),
startDate.with(TemporalAdjusters.next(DayOfWeek.FRIDAY)).toDate()
).filter { !it.isEmpty() }
}
fun saveExams(exams: List<Exam>) {
examDb.insertAll(exams)
}
fun deleteExams(exams: List<Exam>) {
examDb.deleteAll(exams)
}
}

View File

@ -0,0 +1,49 @@
package io.github.wulkanowy.data.repositories.local
import android.content.Context
import io.github.wulkanowy.data.db.SharedPrefHelper
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.reactivex.Completable
import io.reactivex.Maybe
import io.reactivex.Single
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class SessionLocal @Inject constructor(
private val studentDb: StudentDao,
private val semesterDb: SemesterDao,
private val sharedPref: SharedPrefHelper,
private val context: Context) {
companion object {
const val LAST_USER_KEY: String = "last_user_id"
}
val isSessionSaved
get() = sharedPref.getLong(LAST_USER_KEY, defaultValue = 0L) != 0L
fun saveStudent(student: Student): Completable {
return Single.fromCallable { studentDb.insert(student.copy(password = encrypt(student.password, context))) }
.map { sharedPref.putLong(LAST_USER_KEY, it) }
.ignoreElement()
}
fun getLastStudent(): Maybe<Student> {
return studentDb.load(sharedPref.getLong(LAST_USER_KEY, defaultValue = 0))
.map { it.apply { password = decrypt(password) } }
}
fun saveSemesters(semesters: List<Semester>): Completable {
return Single.fromCallable { semesterDb.insertAll(semesters) }.ignoreElement()
}
fun getSemesters(student: Student): Single<List<Semester>> {
return semesterDb.getSemester(student.studentId)
}
}

View File

@ -1,37 +0,0 @@
package io.github.wulkanowy.data.repositories.local
import android.content.Context
import io.github.wulkanowy.data.db.SharedPrefHelper
import io.github.wulkanowy.data.db.dao.StudentDao
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.reactivex.Completable
import io.reactivex.Single
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class StudentLocal @Inject constructor(
private val studentDb: StudentDao,
private val sharedPref: SharedPrefHelper,
private val context: Context) {
companion object {
const val CURRENT_USER_KEY: String = "current_user_id"
}
val isStudentLoggedIn: Boolean
get() = sharedPref.getLong(CURRENT_USER_KEY, 0) != 0L
fun save(student: Student): Completable {
return Single.fromCallable { studentDb.insert(student.copy(password = encrypt(student.password, context))) }
.map { sharedPref.putLong(CURRENT_USER_KEY, it) }
.ignoreElement()
}
fun getCurrentStudent(): Single<Student> {
return studentDb.load(sharedPref.getLong(CURRENT_USER_KEY, defaultValue = 0))
.map { it.apply { password = decrypt(password) } }
}
}

View File

@ -0,0 +1,37 @@
package io.github.wulkanowy.data.repositories.remote
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.utils.extension.toDate
import io.reactivex.Single
import org.threeten.bp.LocalDate
import javax.inject.Inject
class ExamRemote @Inject constructor(private val api: Api) {
fun getExams(semester: Semester, startDate: LocalDate): Single<List<Exam>> {
return Single.just(api.run {
if (diaryId != semester.diaryId) {
diaryId = semester.diaryId
notifyDataChanged()
}
}).flatMap { api.getExams(startDate.toDate()) }
.map { exams ->
exams.map {
Exam(
studentId = semester.studentId,
diaryId = semester.diaryId,
date = it.date,
entryDate = it.entryDate,
subject = it.subject,
group = it.group,
type = it.type,
description = it.description,
teacher = it.teacher,
teacherSymbol = it.teacherSymbol
)
}
}
}
}

View File

@ -0,0 +1,59 @@
package io.github.wulkanowy.data.repositories.remote
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.reactivex.Single
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class SessionRemote @Inject constructor(private val api: Api) {
fun getConnectedStudents(email: String, password: String, symbol: String): Single<List<Student>> {
return Single.just(initApi(Student(email = email, password = password, symbol = symbol)))
.flatMap { _ ->
api.getPupils().map { students ->
students.map {
Student(email = email,
password = password,
symbol = it.symbol,
studentId = it.studentId,
studentName = it.studentName,
schoolId = it.schoolId,
schoolName = it.schoolName)
}
}
}
}
fun getSemesters(student: Student): Single<List<Semester>> {
return Single.just(initApi(student)).flatMap { _ ->
api.getSemesters().map { semesters ->
semesters.map {
Semester(studentId = student.studentId,
diaryId = it.diaryId,
diaryName = it.diaryName,
semesterId = it.semesterId.toString(),
semesterName = it.semesterNumber,
current = it.current)
}
}
}
}
fun initApi(student: Student, checkInit: Boolean = false) {
if (if (checkInit) api.studentId.isEmpty() else true) {
api.run {
email = student.email
password = student.password
symbol = student.symbol
host = "vulcan.net.pl"
schoolId = student.schoolId
studentId = student.studentId
notifyDataChanged()
}
}
}
}

View File

@ -1,32 +0,0 @@
package io.github.wulkanowy.data.repositories.remote
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.data.db.entities.Student
import io.reactivex.Single
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class StudentRemote @Inject constructor(private val api: Api) {
fun getConnectedStudents(email: String, password: String, symbol: String): Single<List<Student>> {
api.let {
it.email = email
it.password = password
it.symbol = symbol
it.host = "vulcan.net.pl"
it.onConfigChange()
}
return api.getPupils().map { students ->
students.map {
Student(email = email,
password = password,
symbol = it.symbol,
studentId = it.studentId,
studentName = it.studentName,
schoolId = it.schoolId,
schoolName = it.schoolName)
}
}
}
}

View File

@ -3,8 +3,9 @@ package io.github.wulkanowy.di
import android.content.Context import android.content.Context
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.WulkanowyApp import io.github.wulkanowy.WulkanowyApp
import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.utils.schedulers.SchedulersManager import io.github.wulkanowy.utils.schedulers.SchedulersManager
import io.github.wulkanowy.utils.schedulers.SchedulersProvider import io.github.wulkanowy.utils.schedulers.SchedulersProvider
@ -18,5 +19,5 @@ internal class AppModule {
fun provideSchedulers(): SchedulersManager = SchedulersProvider() fun provideSchedulers(): SchedulersManager = SchedulersProvider()
@Provides @Provides
fun provideErrorHandler(context: Context): ErrorHandler = ErrorHandler(context.resources) fun provideFlexibleAdapter() = FlexibleAdapter<AbstractFlexibleItem<*>>(null, null, true)
} }

View File

@ -1,6 +1,6 @@
package io.github.wulkanowy.ui.login.form package io.github.wulkanowy.ui.login.form
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.login.LoginErrorHandler import io.github.wulkanowy.ui.login.LoginErrorHandler
import io.github.wulkanowy.utils.DEFAULT_SYMBOL import io.github.wulkanowy.utils.DEFAULT_SYMBOL
@ -10,7 +10,7 @@ import javax.inject.Inject
class LoginFormPresenter @Inject constructor( class LoginFormPresenter @Inject constructor(
private val schedulers: SchedulersManager, private val schedulers: SchedulersManager,
private val errorHandler: LoginErrorHandler, private val errorHandler: LoginErrorHandler,
private val studentRepository: StudentRepository) private val sessionRepository: SessionRepository)
: BasePresenter<LoginFormView>(errorHandler) { : BasePresenter<LoginFormView>(errorHandler) {
private var wasEmpty = false private var wasEmpty = false
@ -22,7 +22,7 @@ class LoginFormPresenter @Inject constructor(
fun attemptLogin(email: String, password: String, symbol: String) { fun attemptLogin(email: String, password: String, symbol: String) {
if (!validateCredentials(email, password, symbol)) return if (!validateCredentials(email, password, symbol)) return
disposable.add(studentRepository.getConnectedStudents(email, password, normalizeSymbol(symbol)) disposable.add(sessionRepository.getConnectedStudents(email, password, normalizeSymbol(symbol))
.observeOn(schedulers.mainThread()) .observeOn(schedulers.mainThread())
.subscribeOn(schedulers.backgroundThread()) .subscribeOn(schedulers.backgroundThread())
.doOnSubscribe { .doOnSubscribe {
@ -34,7 +34,7 @@ class LoginFormPresenter @Inject constructor(
showSoftKeyboard() showSoftKeyboard()
} }
} }
studentRepository.clearCache() sessionRepository.clearCache()
} }
.doFinally { view?.showLoginProgress(false) } .doFinally { view?.showLoginProgress(false) }
.subscribe({ .subscribe({

View File

@ -9,6 +9,7 @@ import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.main.MainActivity import io.github.wulkanowy.ui.main.MainActivity
@ -22,7 +23,11 @@ class LoginOptionsFragment : BaseFragment(), LoginOptionsView {
lateinit var presenter: LoginOptionsPresenter lateinit var presenter: LoginOptionsPresenter
@Inject @Inject
lateinit var loginAdapter: FlexibleAdapter<LoginOptionsItem> lateinit var loginAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
companion object {
fun newInstance() = LoginOptionsFragment()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_login_options, container, false) return inflater.inflate(R.layout.fragment_login_options, container, false)
@ -34,7 +39,13 @@ class LoginOptionsFragment : BaseFragment(), LoginOptionsView {
} }
override fun initRecycler() { override fun initRecycler() {
loginAdapter.setOnItemClickListener { item -> item?.let { presenter.onSelectStudent(it.student) } } loginAdapter.run {
setOnItemClickListener { position ->
(getItem(position) as? LoginOptionsItem)?.let {
presenter.onSelectStudent(it.student)
}
}
}
loginOptionsRecycler.run { loginOptionsRecycler.run {
adapter = loginAdapter adapter = loginAdapter
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = SmoothScrollLinearLayoutManager(context)
@ -47,7 +58,7 @@ class LoginOptionsFragment : BaseFragment(), LoginOptionsView {
override fun updateData(data: List<LoginOptionsItem>) { override fun updateData(data: List<LoginOptionsItem>) {
loginAdapter.run { loginAdapter.run {
updateDataSet(data) updateDataSet(data, true)
} }
} }
@ -66,4 +77,9 @@ class LoginOptionsFragment : BaseFragment(), LoginOptionsView {
override fun showActionBar(show: Boolean) { override fun showActionBar(show: Boolean) {
(activity as AppCompatActivity?)?.supportActionBar?.run { if (show) show() else hide() } (activity as AppCompatActivity?)?.supportActionBar?.run { if (show) show() else hide() }
} }
override fun onDestroyView() {
super.onDestroyView()
presenter.detachView()
}
} }

View File

@ -2,14 +2,14 @@ package io.github.wulkanowy.ui.login.options
import io.github.wulkanowy.data.ErrorHandler import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.utils.schedulers.SchedulersManager import io.github.wulkanowy.utils.schedulers.SchedulersManager
import javax.inject.Inject import javax.inject.Inject
class LoginOptionsPresenter @Inject constructor( class LoginOptionsPresenter @Inject constructor(
private val errorHandler: ErrorHandler, private val errorHandler: ErrorHandler,
private val repository: StudentRepository, private val repository: SessionRepository,
private val schedulers: SchedulersManager) private val schedulers: SchedulersManager)
: BasePresenter<LoginOptionsView>(errorHandler) { : BasePresenter<LoginOptionsView>(errorHandler) {
@ -32,7 +32,7 @@ class LoginOptionsPresenter @Inject constructor(
} }
fun onSelectStudent(student: Student) { fun onSelectStudent(student: Student) {
disposable.add(repository.save(student) disposable.add(repository.saveStudent(student)
.subscribeOn(schedulers.backgroundThread()) .subscribeOn(schedulers.backgroundThread())
.observeOn(schedulers.mainThread()) .observeOn(schedulers.mainThread())
.doOnSubscribe { _ -> .doOnSubscribe { _ ->

View File

@ -0,0 +1,51 @@
package io.github.wulkanowy.ui.main.exam
import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.utils.extension.toFormat
import kotlinx.android.synthetic.main.dialog_exam.*
class ExamDialog : DialogFragment() {
private lateinit var exam: Exam
companion object {
private const val ARGUMENT_KEY = "Item"
fun newInstance(exam: Exam): ExamDialog {
return ExamDialog().apply {
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(DialogFragment.STYLE_NORMAL, R.style.DialogFragmentTheme)
arguments?.run {
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)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
examDialogSubjectValue.text = exam.subject
examDialogTypeValue.text = exam.type
examDialogTeacherValue.text = exam.teacher
examDialogDateValue.text = exam.entryDate.toFormat()
examDialogDescriptionValue.text = exam.description
examDialogClose.setOnClickListener { dismiss() }
}
}

View File

@ -3,17 +3,104 @@ package io.github.wulkanowy.ui.main.exam
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.View.*
import android.view.ViewGroup import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.utils.extension.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_exam.*
import javax.inject.Inject
class ExamFragment : BaseFragment() { class ExamFragment : BaseFragment(), ExamView {
@Inject
lateinit var presenter: ExamPresenter
@Inject
lateinit var examAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
companion object { companion object {
private const val SAVED_DATE_KEY = "CURRENT_DATE"
fun newInstance() = ExamFragment() fun newInstance() = ExamFragment()
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_exam, container, false) return inflater.inflate(R.layout.fragment_exam, container, false)
} }
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
presenter.run {
attachView(this@ExamFragment)
loadData(date = savedInstanceState?.getLong(SAVED_DATE_KEY))
}
}
override fun initView() {
examAdapter.run {
setOnItemClickListener { presenter.onExamItemSelected(getItem(it)) }
}
examRecycler.run {
layoutManager = SmoothScrollLinearLayoutManager(context)
adapter = examAdapter
}
examSwipe.setOnRefreshListener { presenter.loadData(date = null, forceRefresh = true) }
examPreviousButton.setOnClickListener { presenter.loadExamsForPreviousWeek() }
examNextButton.setOnClickListener { presenter.loadExamsForNextWeek()}
}
override fun updateData(data: List<ExamItem>) {
examAdapter.updateDataSet(data, true)
}
override fun updateNavigationWeek(date: String) {
examNavDate.text = date
}
override fun showEmpty(show: Boolean) {
examEmpty.visibility = if (show) VISIBLE else GONE
}
override fun showProgress(show: Boolean) {
examProgress.visibility = if (show) VISIBLE else GONE
}
override fun showContent(show: Boolean) {
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
}
override fun showNextButton(show: Boolean) {
examNextButton.visibility = if (show) VISIBLE else INVISIBLE
}
override fun showExamDialog(exam: Exam) {
ExamDialog.newInstance(exam).show(fragmentManager, exam.toString())
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay())
}
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
presenter.loadData(date = savedInstanceState?.getLong(SAVED_DATE_KEY))
}
override fun onDestroyView() {
super.onDestroyView()
presenter.detachView()
}
} }

View File

@ -0,0 +1,59 @@
package io.github.wulkanowy.ui.main.exam
import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractHeaderItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.ExpandableViewHolder
import io.github.wulkanowy.R
import io.github.wulkanowy.utils.extension.getWeekDayName
import io.github.wulkanowy.utils.extension.toFormat
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.header_exam.*
import org.apache.commons.lang3.StringUtils
import java.util.*
class ExamHeader : AbstractHeaderItem<ExamHeader.ViewHolder>() {
lateinit var date: Date
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?): ViewHolder {
return ViewHolder(view, adapter)
}
override fun getLayoutRes() = R.layout.header_exam
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ExamHeader
if (date != other.date) return false
return true
}
override fun hashCode(): Int {
return date.hashCode()
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder,
position: Int, payloads: MutableList<Any>?) {
holder.run {
examHeaderDay.text = StringUtils.capitalize(date.getWeekDayName())
examHeaderDate.text = date.toFormat()
}
}
class ViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?) : ExpandableViewHolder(view, adapter),
LayoutContainer {
init {
contentView.setOnClickListener(this)
}
override val containerView: View
get() = contentView
}
}

View File

@ -0,0 +1,52 @@
package io.github.wulkanowy.ui.main.exam
import android.support.v7.widget.RecyclerView
import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractSectionableItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Exam
import kotlinx.android.extensions.LayoutContainer
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 {
return ViewHolder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder,
position: Int, payloads: MutableList<Any>?) {
holder.run {
examItemSubject.text = exam.subject
examItemTeacher.text = exam.teacher
examItemType.text = exam.type
}
}
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter),
LayoutContainer {
override val containerView: View
get() = contentView
}
}

View File

@ -0,0 +1,98 @@
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.extension.isHolidays
import io.github.wulkanowy.utils.extension.toFormat
import io.github.wulkanowy.utils.getNearMonday
import io.github.wulkanowy.utils.schedulers.SchedulersManager
import org.threeten.bp.LocalDate
import java.util.*
import javax.inject.Inject
class ExamPresenter @Inject constructor(
private val errorHandler: ErrorHandler,
private val schedulers: SchedulersManager,
private val examRepository: ExamRepository,
private val sessionRepository: SessionRepository
) : BasePresenter<ExamView>(errorHandler) {
var currentDate: LocalDate = getNearMonday(LocalDate.now())
private set
override fun attachView(view: ExamView) {
super.attachView(view)
view.initView()
}
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 ?: getNearMonday(currentDate).toEpochDay())
if (currentDate.isHolidays()) return
disposable.clear()
disposable.add(sessionRepository.getSemesters()
.map { selectSemester(it, -1) }
.flatMap { examRepository.getExams(it, currentDate, 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.toFormat("dd.MM")}-${currentDate.plusDays(4).toFormat("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) })
}
private fun createExamItems(items: Map<Date, List<Exam>>): List<ExamItem> {
return items.flatMap {
val header = ExamHeader().apply { date = it.key }
it.value.reversed().map { item ->
ExamItem(header, item)
}
}
}
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
}
}
}
}
}

View File

@ -0,0 +1,28 @@
package io.github.wulkanowy.ui.main.exam
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.ui.base.BaseView
import org.threeten.bp.LocalDate
interface ExamView : BaseView {
fun initView()
fun updateData(data: List<ExamItem>)
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

@ -21,12 +21,12 @@ class SplashActivity : BaseActivity(), SplashView {
presenter.detachView() presenter.detachView()
} }
override fun openLoginActivity() { override fun openLoginView() {
startActivity(LoginActivity.getStartIntent(this)) startActivity(LoginActivity.getStartIntent(this))
finish() finish()
} }
override fun openMainActivity() { override fun openMainView() {
startActivity(MainActivity.getStartIntent(this)) startActivity(MainActivity.getStartIntent(this))
finish() finish()
} }

View File

@ -1,16 +1,16 @@
package io.github.wulkanowy.ui.splash package io.github.wulkanowy.ui.splash
import io.github.wulkanowy.data.ErrorHandler import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import javax.inject.Inject import javax.inject.Inject
class SplashPresenter @Inject constructor(private val studentRepository: StudentRepository, class SplashPresenter @Inject constructor(private val sessionRepository: SessionRepository,
errorHandler: ErrorHandler) errorHandler: ErrorHandler)
: BasePresenter<SplashView>(errorHandler) { : BasePresenter<SplashView>(errorHandler) {
override fun attachView(view: SplashView) { override fun attachView(view: SplashView) {
super.attachView(view) super.attachView(view)
view.run { if (studentRepository.isStudentLoggedIn) openMainActivity() else openLoginActivity() } view.run { if (sessionRepository.isSessionSaved) openMainView() else openLoginView() }
} }
} }

View File

@ -4,7 +4,7 @@ import io.github.wulkanowy.ui.base.BaseView
interface SplashView : BaseView { interface SplashView : BaseView {
fun openLoginActivity() fun openLoginView()
fun openMainActivity() fun openMainView()
} }

View File

@ -4,6 +4,7 @@ import org.threeten.bp.DayOfWeek.*
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import org.threeten.bp.Year import org.threeten.bp.Year
import org.threeten.bp.format.DateTimeFormatter import org.threeten.bp.format.DateTimeFormatter
import org.threeten.bp.temporal.TemporalAdjuster
import org.threeten.bp.temporal.TemporalAdjusters import org.threeten.bp.temporal.TemporalAdjusters
import java.util.* import java.util.*
@ -68,29 +69,35 @@ fun isDateInWeek(firstWeekDay: LocalDate, date: LocalDate): Boolean {
return date.isAfter(firstWeekDay.minusDays(1)) && date.isBefore(firstWeekDay.plusDays(5)) return date.isAfter(firstWeekDay.minusDays(1)) && date.isBefore(firstWeekDay.plusDays(5))
} }
fun getNearMonday(date: LocalDate): LocalDate {
return when(date.dayOfWeek) {
MONDAY -> date
SATURDAY, SUNDAY -> date.with(TemporalAdjusters.next(MONDAY))
else -> date.with(TemporalAdjusters.previous(MONDAY))
}
}
/** /**
* [Dz.U. 2016 poz. 1335](http://prawo.sejm.gov.pl/isap.nsf/DocDetails.xsp?id=WDU20160001335) * [Dz.U. 2016 poz. 1335](http://prawo.sejm.gov.pl/isap.nsf/DocDetails.xsp?id=WDU20160001335)
*/ */
fun isHolidays(): Boolean = isHolidays(LocalDate.now(), Year.now().value) fun isHolidays(): Boolean = isHolidays(LocalDate.now())
fun isHolidays(day: LocalDate, year: Int): Boolean { fun isHolidays(day: LocalDate): Boolean {
return day.isAfter(getLastSchoolDay(year)) && day.isBefore(getFirstSchoolDay(year)) return day.isAfter(getLastSchoolDay(day.year)) && day.isBefore(getFirstSchoolDay(day.year))
} }
fun getFirstSchoolDay(year: Int): LocalDate? { fun getFirstSchoolDay(year: Int): LocalDate {
val firstSeptember = LocalDate.of(year, 9, 1) val firstSeptember = LocalDate.of(year, 9, 1)
return when (firstSeptember.dayOfWeek) { return when (firstSeptember.dayOfWeek) {
FRIDAY, FRIDAY,
SATURDAY, SATURDAY,
SUNDAY -> firstSeptember.with(TemporalAdjusters.firstInMonth(MONDAY)) SUNDAY -> firstSeptember.with(TemporalAdjusters.firstInMonth(MONDAY))
else -> { else -> firstSeptember
firstSeptember
}
} }
} }
fun getLastSchoolDay(year: Int): LocalDate? { fun getLastSchoolDay(year: Int): LocalDate {
return LocalDate return LocalDate
.of(year, 6, 20) .of(year, 6, 20)
.with(TemporalAdjusters.next(FRIDAY)) .with(TemporalAdjusters.next(FRIDAY))

View File

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

View File

@ -3,9 +3,13 @@ package io.github.wulkanowy.utils.extension
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
fun <K : AbstractFlexibleItem<*>, T : FlexibleAdapter<K>> T.setOnItemClickListener(listener: (K?) -> Unit) { fun FlexibleAdapter<*>.setOnItemClickListener(listener: (position: Int) -> Unit) {
addListener(FlexibleAdapter.OnItemClickListener { _, position -> addListener(FlexibleAdapter.OnItemClickListener { _, position ->
listener(getItem(position)) listener(position)
true true
}) })
} }
fun FlexibleAdapter<*>.setOnUpdateListener(listener: (size: Int) -> Unit) {
addListener(FlexibleAdapter.OnUpdateListener { listener(it) })
}

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@ -8,171 +7,106 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minWidth="300dp" android:minWidth="300dp"
android:orientation="vertical"> android:orientation="vertical"
android:padding="20dp">
<RelativeLayout <TextView
android:id="@+id/timetable_dialog_relative_layout" android:id="@+id/examDialogSubject"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingBottom="20dp" android:text="@string/all_subject"
android:paddingEnd="20dp" android:textIsSelectable="true"
android:paddingLeft="20dp" android:textSize="17sp" />
android:paddingRight="20dp"
android:paddingStart="20dp"
android:paddingTop="10dp"
tools:ignore="UselessParent">
<TextView <TextView
android:id="@+id/exams_dialog_details" android:id="@+id/examDialogSubjectValue"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentLeft="true" android:layout_marginTop="3dp"
android:layout_alignParentStart="true" android:text="@string/all_no_data"
android:layout_alignParentTop="true" android:textIsSelectable="true"
android:layout_gravity="start" android:textSize="12sp" />
android:gravity="center_vertical"
android:maxLines="5"
android:minHeight="60dp"
android:minLines="2"
android:paddingTop="10dp"
android:text="@string/all_details"
android:textIsSelectable="true"
android:textSize="20sp" />
<TextView <TextView
android:id="@+id/exams_dialog_subject" android:id="@+id/examDialogType"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentLeft="true" android:layout_marginTop="10dp"
android:layout_alignParentStart="true" android:text="@string/exam_type"
android:layout_below="@+id/exams_dialog_details" android:textIsSelectable="true"
android:layout_marginTop="10dp" android:textSize="17sp" />
android:text="@string/all_subject"
android:textIsSelectable="true"
android:textSize="17sp" />
<TextView <TextView
android:id="@+id/exams_dialog_subject_value" android:id="@+id/examDialogTypeValue"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentLeft="true" android:layout_marginTop="3dp"
android:layout_alignParentStart="true" android:text="@string/all_no_data"
android:layout_below="@+id/exams_dialog_subject" android:textIsSelectable="true"
android:layout_marginTop="3dp" android:textSize="12sp" />
android:text="@string/all_no_data"
android:textIsSelectable="true"
android:textSize="12sp" />
<TextView <TextView
android:id="@+id/exams_dialog_type" android:id="@+id/examDialogTeacher"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentLeft="true" android:layout_marginTop="10dp"
android:layout_alignParentStart="true" android:text="@string/all_teacher"
android:layout_below="@+id/exams_dialog_subject_value" android:textSize="17sp" />
android:layout_marginTop="10dp"
android:text="@string/exam_type"
android:textIsSelectable="true"
android:textSize="17sp" />
<TextView <TextView
android:id="@+id/exams_dialog_type_value" android:id="@+id/examDialogTeacherValue"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentLeft="true" android:layout_marginTop="3dp"
android:layout_alignParentStart="true" android:text="@string/all_no_data"
android:layout_below="@+id/exams_dialog_type" android:textIsSelectable="true"
android:layout_marginTop="3dp" android:textSize="12sp" />
android:text="@string/all_no_data"
android:textIsSelectable="true"
android:textSize="12sp" />
<TextView <TextView
android:id="@+id/exams_dialog_teacher" android:id="@+id/examDialogDate"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentLeft="true" android:layout_marginTop="10dp"
android:layout_alignParentStart="true" android:text="@string/exam_entry_date"
android:layout_below="@+id/exams_dialog_type_value" android:textSize="17sp" />
android:layout_marginTop="10dp"
android:text="@string/all_teacher"
android:textSize="17sp" />
<TextView <TextView
android:id="@+id/exams_dialog_teacher_value" android:id="@+id/examDialogDateValue"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentLeft="true" android:layout_marginTop="3dp"
android:layout_alignParentStart="true" android:text="@string/all_no_data"
android:layout_below="@+id/exams_dialog_teacher" android:textIsSelectable="true"
android:layout_marginTop="3dp" android:textSize="12sp" />
android:text="@string/all_no_data"
android:textIsSelectable="true"
android:textSize="12sp" />
<TextView <TextView
android:id="@+id/exams_dialog_date" android:id="@+id/examDialogDescription"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentLeft="true" android:layout_marginTop="10dp"
android:layout_alignParentStart="true" android:text="@string/all_description"
android:layout_below="@+id/exams_dialog_teacher_value" android:textSize="17sp" />
android:layout_marginTop="10dp"
android:text="@string/exam_entry_date"
android:textSize="17sp" />
<TextView <TextView
android:id="@+id/exams_dialog_date_value" android:id="@+id/examDialogDescriptionValue"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentLeft="true" android:layout_marginTop="3dp"
android:layout_alignParentStart="true" android:text="@string/all_no_data"
android:layout_below="@+id/exams_dialog_date" android:textIsSelectable="true"
android:layout_marginTop="3dp" android:textSize="12sp" />
android:text="@string/all_no_data"
android:textIsSelectable="true"
android:textSize="12sp" />
<TextView <Button
android:id="@+id/exams_dialog_description" android:id="@+id/examDialogClose"
android:layout_width="wrap_content" style="@style/Widget.AppCompat.Button.Borderless"
android:layout_height="wrap_content" android:layout_width="wrap_content"
android:layout_alignParentLeft="true" android:layout_height="wrap_content"
android:layout_alignParentStart="true" android:layout_gravity="end"
android:layout_below="@+id/exams_dialog_date_value" android:layout_marginTop="15dp"
android:layout_marginTop="10dp" android:padding="0dp"
android:text="@string/all_description" android:text="@string/all_close"
android:textSize="17sp" /> android:textAllCaps="true"
android:textSize="15sp" />
<TextView
android:id="@+id/exams_dialog_description_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/exams_dialog_description"
android:layout_marginTop="3dp"
android:text="@string/all_no_data"
android:textIsSelectable="true"
android:textSize="12sp" />
<Button
android:id="@+id/exams_dialog_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignTop="@+id/exams_dialog_description_value"
android:layout_marginTop="25dp"
android:background="?attr/selectableItemBackground"
android:focusable="true"
android:text="@string/all_close"
android:textColor="?android:attr/android:textColorSecondary"
android:textAllCaps="true"
android:textSize="15sp" />
</RelativeLayout>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@ -1,30 +1,99 @@
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/examContainer" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_alignParentBottom="true"> android:orientation="vertical">
<TextView <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="0dp"
android:gravity="center" android:layout_weight="1"
android:text="Exam" /> android:orientation="vertical">
<!--<RelativeLayout <ProgressBar
android:layout_width="match_parent" android:id="@+id/examProgress"
android:layout_height="match_parent"> android:layout_width="wrap_content"
<android.support.design.widget.TabLayout
android:id="@+id/exams_fragment_tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:tabMinWidth="125dp" android:layout_gravity="center"
app:tabMode="scrollable" /> android:indeterminate="true" />
<android.support.v4.view.ViewPager <android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/exams_fragment_viewpager" android:id="@+id/examSwipe"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/examRecycler"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v4.widget.SwipeRefreshLayout>
<LinearLayout
android:id="@+id/examEmpty"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_below="@id/exams_fragment_tab_layout" /> android:gravity="center"
</RelativeLayout>--> android:orientation="vertical"
</android.support.design.widget.CoordinatorLayout> android:padding="10dp"
android:visibility="gone">
<android.support.v7.widget.AppCompatImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="100dp"
android:minWidth="100dp"
app:srcCompat="@drawable/ic_menu_main_exam_24dp"
app:tint="?android:attr/textColorPrimary"
tools:ignore="contentDescription" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center"
android:text="@string/exam_no_items"
android:textSize="20sp" />
</LinearLayout>
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:orientation="horizontal">
<Button
android:id="@+id/examPreviousButton"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawablePadding="4dp"
android:gravity="start|center"
android:text="@string/prev"
android:textAlignment="gravity" />
<TextView
android:id="@+id/examNavDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/app_name" />
<Button
android:id="@+id/examNextButton"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_weight="1"
android:drawablePadding="4dp"
android:gravity="end|center"
android:text="@string/next"
android:textAlignment="gravity" />
</LinearLayout>
</LinearLayout>

View File

@ -1,64 +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/exams_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/exams_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/exams_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/exams_tab_fragment_no_item_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/exams_tab_fragment_no_item_text"
android:layout_centerHorizontal="true"
android:layout_marginTop="40dp"
android:minHeight="100dp"
android:minWidth="100dp"
app:srcCompat="@drawable/ic_menu_main_exam_24dp"
app:tint="?android:attr/textColorPrimary"
tools:ignore="contentDescription" />
<TextView
android:id="@+id/exams_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/exam_no_items"
android:textSize="20sp" />
</RelativeLayout>
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/exams_tab_fragment_swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/exams_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

@ -10,7 +10,7 @@
android:paddingTop="10dp"> android:paddingTop="10dp">
<TextView <TextView
android:id="@+id/exams_header_name" android:id="@+id/examHeaderDay"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerVertical="true" android:layout_centerVertical="true"
@ -18,7 +18,7 @@
android:textSize="18sp" /> android:textSize="18sp" />
<TextView <TextView
android:id="@+id/exams_header_date" android:id="@+id/examHeaderDate"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
@ -26,8 +26,8 @@
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginLeft="10dp" android:layout_marginLeft="10dp"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
android:layout_toEndOf="@id/exams_header_name" android:layout_toEndOf="@id/examHeaderDay"
android:layout_toRightOf="@id/exams_header_name" android:layout_toRightOf="@id/examHeaderDay"
android:gravity="end" android:gravity="end"
android:text="@string/app_name" android:text="@string/app_name"
android:textSize="13sp" /> android:textSize="13sp" />

View File

@ -1,56 +1,47 @@
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/exams_subitem_container" android:id="@+id/exams_subitem_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="60dp" android:foreground="?attr/selectableItemBackgroundBorderless">
card_view:cardElevation="0dp">
<RelativeLayout <TextView
android:layout_width="match_parent" android:id="@+id/examItemSubject"
android:layout_height="match_parent" android:layout_width="wrap_content"
android:background="@drawable/ic_all_divider" android:layout_height="wrap_content"
android:foreground="?attr/selectableItemBackgroundBorderless"> android:layout_marginLeft="20dp"
android:layout_marginStart="20dp"
android:layout_marginTop="10dp"
android:text="@string/app_name"
android:textSize="15sp" />
<TextView <TextView
android:id="@+id/exams_subitem_subject" android:id="@+id/examItemType"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="20dp" android:layout_alignLeft="@id/examItemSubject"
android:layout_marginStart="20dp" android:layout_alignStart="@id/examItemSubject"
android:layout_marginTop="10dp" android:layout_below="@id/examItemSubject"
android:text="@string/app_name" android:layout_marginBottom="5dp"
android:textSize="15sp" /> android:layout_marginTop="5dp"
android:text="@string/app_name"
android:textSize="13sp" />
<TextView <TextView
android:id="@+id/exams_subitems_type" android:id="@+id/examItemTeacher"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignLeft="@id/exams_subitem_subject" android:layout_alignParentEnd="true"
android:layout_alignStart="@id/exams_subitem_subject" android:layout_alignParentRight="true"
android:layout_below="@id/exams_subitem_subject" android:layout_below="@id/examItemSubject"
android:layout_marginBottom="5dp" android:layout_marginBottom="10dp"
android:layout_marginTop="5dp" android:layout_marginEnd="20dp"
android:text="@string/app_name" android:layout_marginLeft="10dp"
android:textSize="13sp" /> android:layout_marginRight="20dp"
android:layout_marginStart="10dp"
<TextView android:layout_marginTop="5dp"
android:id="@+id/exams_subitems_teacher" android:layout_toEndOf="@id/examItemType"
android:layout_width="wrap_content" android:layout_toRightOf="@id/examItemType"
android:layout_height="wrap_content" android:gravity="end"
android:layout_alignParentEnd="true" android:text="@string/app_name"
android:layout_alignParentRight="true" android:textSize="13sp" />
android:layout_below="@id/exams_subitem_subject" </RelativeLayout>
android:layout_marginBottom="10dp"
android:layout_marginEnd="20dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="20dp"
android:layout_marginStart="10dp"
android:layout_marginTop="5dp"
android:layout_toEndOf="@id/exams_subitems_type"
android:layout_toRightOf="@id/exams_subitems_type"
android:gravity="end"
android:text="@string/app_name"
android:textSize="13sp" />
</RelativeLayout>
</android.support.v7.widget.CardView>

View File

@ -163,4 +163,7 @@
<string name="all_sync_fail">Podczas synchronizacji wystąpił błąd</string> <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_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="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> </resources>

View File

@ -156,4 +156,6 @@
<string name="all_sync_fail">There was an error during synchronization</string> <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_timeout">Too long wait for connection</string>
<string name="all_login_failed">Login is failed. Try again or restart the app</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> </resources>

View File

@ -36,4 +36,12 @@
<item name="android:windowBackground">@drawable/layer_splash_background</item> <item name="android:windowBackground">@drawable/layer_splash_background</item>
</style> </style>
<style name="DialogFragmentTheme" parent="Theme.AppCompat.DayNight.Dialog.Alert">
<item name="android:textColorTertiary">@android:color/primary_text_light</item>
<item name="windowActionBar">false</item>
<item name="windowNoTitle">false</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">false</item>
</style>
</resources> </resources>

View File

@ -0,0 +1,56 @@
package io.github.wulkanowy.data.repositories.remote
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.api.exams.Exam
import io.github.wulkanowy.data.db.entities.Semester
import io.reactivex.Single
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers.any
import org.mockito.Mock
import org.mockito.Mockito.doReturn
import org.mockito.MockitoAnnotations
import org.threeten.bp.LocalDate
import java.sql.Date
class ExamRemoteTest {
@Mock
private lateinit var mockApi: Api
@Mock
private lateinit var semesterMock: Semester
@Before
fun initApi() {
MockitoAnnotations.initMocks(this)
}
@Test
fun getExamsTest() {
doReturn(Single.just(listOf(
getExam("2018-09-10"),
getExam("2018-09-17")
))).`when`(mockApi).getExams(any())
doReturn("1").`when`(semesterMock).studentId
doReturn("1").`when`(semesterMock).diaryId
val exams = ExamRemote(mockApi).getExams(semesterMock, LocalDate.of(2018, 9, 10)).blockingGet()
assertEquals(2, exams.size)
}
private fun getExam(dateString: String): Exam {
return Exam().apply {
subject = ""
group = ""
type = ""
description = ""
teacher = ""
teacherSymbol = ""
date = Date.valueOf(dateString)
entryDate = Date.valueOf(dateString)
}
}
}

View File

@ -25,7 +25,7 @@ class StudentRemoteTest {
doReturn(Single.just(listOf(Pupil("", "", "", "test", "", "")))) doReturn(Single.just(listOf(Pupil("", "", "", "test", "", ""))))
.`when`(mockApi).getPupils() .`when`(mockApi).getPupils()
val students = StudentRemote(mockApi).getConnectedStudents("", "", "").blockingGet() val students = SessionRemote(mockApi).getConnectedStudents("", "", "").blockingGet()
assertEquals(1, students.size) assertEquals(1, students.size)
assertEquals("test", students.first().studentName) assertEquals("test", students.first().studentName)
} }

View File

@ -2,7 +2,7 @@ package io.github.wulkanowy.ui.login.form
import io.github.wulkanowy.TestSchedulers import io.github.wulkanowy.TestSchedulers
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.SessionRepository
import io.github.wulkanowy.ui.login.LoginErrorHandler import io.github.wulkanowy.ui.login.LoginErrorHandler
import io.reactivex.Single import io.reactivex.Single
import org.junit.Before import org.junit.Before
@ -18,7 +18,7 @@ class LoginFormPresenterTest {
lateinit var loginFormView: LoginFormView lateinit var loginFormView: LoginFormView
@Mock @Mock
lateinit var repository: StudentRepository lateinit var repository: SessionRepository
@Mock @Mock
lateinit var errorHandler: LoginErrorHandler lateinit var errorHandler: LoginErrorHandler

View File

@ -3,7 +3,7 @@ package io.github.wulkanowy.ui.login.options
import io.github.wulkanowy.TestSchedulers import io.github.wulkanowy.TestSchedulers
import io.github.wulkanowy.data.ErrorHandler import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.SessionRepository
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Single import io.reactivex.Single
import org.junit.Before import org.junit.Before
@ -21,7 +21,7 @@ class LoginOptionsPresenterTest {
lateinit var loginOptionsView: LoginOptionsView lateinit var loginOptionsView: LoginOptionsView
@Mock @Mock
lateinit var repository: StudentRepository lateinit var repository: SessionRepository
private lateinit var presenter: LoginOptionsPresenter private lateinit var presenter: LoginOptionsPresenter
@ -62,7 +62,7 @@ class LoginOptionsPresenterTest {
@Test @Test
fun onSelectedStudentTest() { fun onSelectedStudentTest() {
doReturn(Completable.complete()).`when`(repository).save(testStudent) doReturn(Completable.complete()).`when`(repository).saveStudent(testStudent)
presenter.onSelectStudent(testStudent) presenter.onSelectStudent(testStudent)
verify(loginOptionsView).showLoginProgress(true) verify(loginOptionsView).showLoginProgress(true)
verify(loginOptionsView).openMainView() verify(loginOptionsView).openMainView()
@ -71,9 +71,9 @@ class LoginOptionsPresenterTest {
@Test @Test
fun onSelectedStudentErrorTest() { fun onSelectedStudentErrorTest() {
doReturn(Completable.error(testException)).`when`(repository).save(testStudent) doReturn(Completable.error(testException)).`when`(repository).saveStudent(testStudent)
presenter.onSelectStudent(testStudent) presenter.onSelectStudent(testStudent)
verify(loginOptionsView).showLoginProgress(true) verify(loginOptionsView).showLoginProgress(true)
verify(errorHandler).proceed(testException) verify(errorHandler).proceed(testException)
} }
} }

View File

@ -1,7 +1,7 @@
package io.github.wulkanowy.ui.splash package io.github.wulkanowy.ui.splash
import io.github.wulkanowy.data.ErrorHandler import io.github.wulkanowy.data.ErrorHandler
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.SessionRepository
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.mockito.Mock import org.mockito.Mock
@ -15,7 +15,7 @@ class SplashPresenterTest {
lateinit var splashView: SplashView lateinit var splashView: SplashView
@Mock @Mock
lateinit var studentRepository: StudentRepository lateinit var studentRepository: SessionRepository
@Mock @Mock
lateinit var errorHandler: ErrorHandler lateinit var errorHandler: ErrorHandler
@ -30,15 +30,15 @@ class SplashPresenterTest {
@Test @Test
fun testOpenLoginView() { fun testOpenLoginView() {
doReturn(false).`when`(studentRepository).isStudentLoggedIn doReturn(false).`when`(studentRepository).isSessionSaved
presenter.attachView(splashView) presenter.attachView(splashView)
verify(splashView).openLoginActivity() verify(splashView).openLoginView()
} }
@Test @Test
fun testMainMainView() { fun testMainMainView() {
doReturn(true).`when`(studentRepository).isStudentLoggedIn doReturn(true).`when`(studentRepository).isSessionSaved
presenter.attachView(splashView) presenter.attachView(splashView)
verify(splashView).openMainActivity() verify(splashView).openMainView()
} }
} }

View File

@ -86,55 +86,55 @@ class TimeUtilsTest {
} }
@Test fun isHolidaysInSchoolEndTest() { @Test fun isHolidaysInSchoolEndTest() {
assertFalse(isHolidays(LocalDate.of(2017, 6, 23), 2017)) assertFalse(isHolidays(LocalDate.of(2017, 6, 23)))
assertFalse(isHolidays(LocalDate.of(2018, 6, 22), 2018)) assertFalse(isHolidays(LocalDate.of(2018, 6, 22)))
assertFalse(isHolidays(LocalDate.of(2019, 6, 21), 2019)) assertFalse(isHolidays(LocalDate.of(2019, 6, 21)))
assertFalse(isHolidays(LocalDate.of(2020, 6, 26), 2020)) assertFalse(isHolidays(LocalDate.of(2020, 6, 26)))
assertFalse(isHolidays(LocalDate.of(2021, 6, 25), 2021)) assertFalse(isHolidays(LocalDate.of(2021, 6, 25)))
assertFalse(isHolidays(LocalDate.of(2022, 6, 24), 2022)) assertFalse(isHolidays(LocalDate.of(2022, 6, 24)))
assertFalse(isHolidays(LocalDate.of(2023, 6, 23), 2023)) assertFalse(isHolidays(LocalDate.of(2023, 6, 23)))
assertFalse(isHolidays(LocalDate.of(2024, 6, 21), 2024)) assertFalse(isHolidays(LocalDate.of(2024, 6, 21)))
assertFalse(isHolidays(LocalDate.of(2025, 6, 27), 2025)) assertFalse(isHolidays(LocalDate.of(2025, 6, 27)))
} }
@Test fun isHolidaysInHolidaysStartTest() { @Test fun isHolidaysInHolidaysStartTest() {
assertTrue(isHolidays(LocalDate.of(2017, 6, 24), 2017)) assertTrue(isHolidays(LocalDate.of(2017, 6, 24)))
assertTrue(isHolidays(LocalDate.of(2018, 6, 23), 2018)) assertTrue(isHolidays(LocalDate.of(2018, 6, 23)))
assertTrue(isHolidays(LocalDate.of(2019, 6, 22), 2019)) assertTrue(isHolidays(LocalDate.of(2019, 6, 22)))
assertTrue(isHolidays(LocalDate.of(2020, 6, 27), 2020)) assertTrue(isHolidays(LocalDate.of(2020, 6, 27)))
assertTrue(isHolidays(LocalDate.of(2021, 6, 26), 2021)) assertTrue(isHolidays(LocalDate.of(2021, 6, 26)))
assertTrue(isHolidays(LocalDate.of(2022, 6, 25), 2022)) assertTrue(isHolidays(LocalDate.of(2022, 6, 25)))
assertTrue(isHolidays(LocalDate.of(2023, 6, 24), 2023)) assertTrue(isHolidays(LocalDate.of(2023, 6, 24)))
assertTrue(isHolidays(LocalDate.of(2024, 6, 22), 2024)) assertTrue(isHolidays(LocalDate.of(2024, 6, 22)))
assertTrue(isHolidays(LocalDate.of(2025, 6, 28), 2025)) assertTrue(isHolidays(LocalDate.of(2025, 6, 28)))
} }
@Test fun isHolidaysInHolidaysEndTest() { @Test fun isHolidaysInHolidaysEndTest() {
assertTrue(isHolidays(LocalDate.of(2017, 9, 1), 2017)) // friday assertTrue(isHolidays(LocalDate.of(2017, 9, 1))) // friday
assertTrue(isHolidays(LocalDate.of(2017, 9, 2), 2017)) // saturday assertTrue(isHolidays(LocalDate.of(2017, 9, 2))) // saturday
assertTrue(isHolidays(LocalDate.of(2017, 9, 3), 2017)) // sunday assertTrue(isHolidays(LocalDate.of(2017, 9, 3))) // sunday
assertTrue(isHolidays(LocalDate.of(2018, 9, 1), 2018)) // saturday assertTrue(isHolidays(LocalDate.of(2018, 9, 1))) // saturday
assertTrue(isHolidays(LocalDate.of(2018, 9, 2), 2018)) // sunday assertTrue(isHolidays(LocalDate.of(2018, 9, 2))) // sunday
assertTrue(isHolidays(LocalDate.of(2019, 9, 1), 2019)) // sunday assertTrue(isHolidays(LocalDate.of(2019, 9, 1))) // sunday
assertTrue(isHolidays(LocalDate.of(2020, 8, 31), 2020)) // monday assertTrue(isHolidays(LocalDate.of(2020, 8, 31))) // monday
assertTrue(isHolidays(LocalDate.of(2021, 8, 31), 2021)) // tuesday assertTrue(isHolidays(LocalDate.of(2021, 8, 31))) // tuesday
assertTrue(isHolidays(LocalDate.of(2022, 8, 31), 2022)) // wednesday assertTrue(isHolidays(LocalDate.of(2022, 8, 31))) // wednesday
assertTrue(isHolidays(LocalDate.of(2023, 9, 1), 2023)) // friday assertTrue(isHolidays(LocalDate.of(2023, 9, 1))) // friday
assertTrue(isHolidays(LocalDate.of(2023, 9, 2), 2023)) // saturday assertTrue(isHolidays(LocalDate.of(2023, 9, 2))) // saturday
assertTrue(isHolidays(LocalDate.of(2023, 9, 3), 2023)) // sunday assertTrue(isHolidays(LocalDate.of(2023, 9, 3))) // sunday
assertTrue(isHolidays(LocalDate.of(2024, 9, 1), 2024)) // sunday assertTrue(isHolidays(LocalDate.of(2024, 9, 1))) // sunday
assertTrue(isHolidays(LocalDate.of(2025, 8, 31), 2025)) // sunday assertTrue(isHolidays(LocalDate.of(2025, 8, 31))) // sunday
} }
@Test fun isHolidaysInSchoolStartTest() { @Test fun isHolidaysInSchoolStartTest() {
assertFalse(isHolidays(LocalDate.of(2017, 9, 4), 2017)) // monday assertFalse(isHolidays(LocalDate.of(2017, 9, 4))) // monday
assertFalse(isHolidays(LocalDate.of(2018, 9, 3), 2018)) // monday assertFalse(isHolidays(LocalDate.of(2018, 9, 3))) // monday
assertFalse(isHolidays(LocalDate.of(2019, 9, 2), 2019)) // monday assertFalse(isHolidays(LocalDate.of(2019, 9, 2))) // monday
assertFalse(isHolidays(LocalDate.of(2020, 9, 1), 2020)) // tuesday assertFalse(isHolidays(LocalDate.of(2020, 9, 1))) // tuesday
assertFalse(isHolidays(LocalDate.of(2021, 9, 1), 2021)) // wednesday assertFalse(isHolidays(LocalDate.of(2021, 9, 1))) // wednesday
assertFalse(isHolidays(LocalDate.of(2022, 9, 1), 2022)) // thursday assertFalse(isHolidays(LocalDate.of(2022, 9, 1))) // thursday
assertFalse(isHolidays(LocalDate.of(2023, 9, 4), 2023)) // monday assertFalse(isHolidays(LocalDate.of(2023, 9, 4))) // monday
assertFalse(isHolidays(LocalDate.of(2024, 9, 2), 2024)) // monday assertFalse(isHolidays(LocalDate.of(2024, 9, 2))) // monday
assertFalse(isHolidays(LocalDate.of(2025, 9, 1), 2025)) // monday assertFalse(isHolidays(LocalDate.of(2025, 9, 1))) // monday
} }
} }

View File

@ -1,5 +1,5 @@
buildscript { buildscript {
ext.kotlin_version = '1.2.61' ext.kotlin_version = '1.2.70'
repositories { repositories {
mavenCentral() mavenCentral()
google() google()