Add completed lessons (#236)

This commit is contained in:
Mikołaj Pich 2019-02-13 19:21:27 +01:00 committed by Rafał Borcz
parent 52ed7dcb6c
commit 297502056c
34 changed files with 1172 additions and 17 deletions

View File

@ -11,9 +11,9 @@ cache:
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/
#branches:
# only:
# - master
branches:
only:
- master
android:
licenses:

View File

@ -73,7 +73,7 @@ play {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation('com.github.wulkanowy:api:f941c4b1c7') { exclude module: "threetenbp" }
implementation('com.github.wulkanowy:api:0bbd246778') { exclude module: "threetenbp" }
implementation "androidx.legacy:legacy-support-v4:1.0.0"
implementation "androidx.appcompat:appcompat:1.0.2"

View File

@ -0,0 +1,57 @@
package io.github.wulkanowy.data.repositories.local
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.entities.CompletedLesson
import io.github.wulkanowy.data.db.entities.Semester
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.threeten.bp.LocalDate
import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
class CompletedLessonsLocalTest {
private lateinit var completedLessonsLocal: CompletedLessonsLocal
private lateinit var testDb: AppDatabase
@Before
fun createDb() {
testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java)
.build()
completedLessonsLocal = CompletedLessonsLocal(testDb.completedLessonsDao)
}
@After
fun closeDb() {
testDb.close()
}
@Test
fun saveAndReadTest() {
completedLessonsLocal.saveCompletedLessons(listOf(
getCompletedLesson(LocalDate.of(2018, 9, 10), 1),
getCompletedLesson(LocalDate.of(2018, 9, 14), 2),
getCompletedLesson(LocalDate.of(2018, 9, 17), 3)
))
val completed = completedLessonsLocal
.getCompletedLessons(Semester(1, 1, 2, "", 3, 1),
LocalDate.of(2018, 9, 10),
LocalDate.of(2018, 9, 14)
)
.blockingGet()
assertEquals(2, completed.size)
assertEquals(completed[0].date, LocalDate.of(2018, 9, 10))
assertEquals(completed[1].date, LocalDate.of(2018, 9, 14))
}
private fun getCompletedLesson(date: LocalDate, number: Int): CompletedLesson {
return CompletedLesson(1, 2, date, number, "", "", "", "", "", "", "")
}
}

View File

@ -117,4 +117,8 @@ internal class RepositoryModule {
@Singleton
@Provides
fun provideLuckyNumberDao(database: AppDatabase) = database.luckyNumberDao
@Singleton
@Provides
fun provideCompletedLessonsDao(database: AppDatabase) = database.completedLessonsDao
}

View File

@ -15,6 +15,7 @@ import io.github.wulkanowy.data.db.dao.HomeworkDao
import io.github.wulkanowy.data.db.dao.LuckyNumberDao
import io.github.wulkanowy.data.db.dao.MessagesDao
import io.github.wulkanowy.data.db.dao.NoteDao
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
import io.github.wulkanowy.data.db.dao.SemesterDao
import io.github.wulkanowy.data.db.dao.StudentDao
import io.github.wulkanowy.data.db.dao.SubjectDao
@ -28,11 +29,13 @@ import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.Note
import io.github.wulkanowy.data.db.entities.CompletedLesson
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.Subject
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.db.migrations.Migration2
import io.github.wulkanowy.data.db.migrations.Migration3
import javax.inject.Singleton
@Singleton
@ -50,9 +53,10 @@ import javax.inject.Singleton
Note::class,
Homework::class,
Subject::class,
LuckyNumber::class
LuckyNumber::class,
CompletedLesson::class
],
version = 2,
version = 3,
exportSchema = false
)
@TypeConverters(Converters::class)
@ -63,7 +67,8 @@ abstract class AppDatabase : RoomDatabase() {
return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
.setJournalMode(TRUNCATE)
.addMigrations(
Migration2()
Migration2(),
Migration3()
)
.build()
}
@ -94,4 +99,6 @@ abstract class AppDatabase : RoomDatabase() {
abstract val subjectDao: SubjectDao
abstract val luckyNumberDao: LuckyNumberDao
abstract val completedLessonsDao: CompletedLessonsDao
}

View File

@ -0,0 +1,24 @@
package io.github.wulkanowy.data.db.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import io.github.wulkanowy.data.db.entities.CompletedLesson
import io.reactivex.Maybe
import org.threeten.bp.LocalDate
import javax.inject.Singleton
@Singleton
@Dao
interface CompletedLessonsDao {
@Insert
fun insertAll(exams: List<CompletedLesson>): List<Long>
@Delete
fun deleteAll(exams: List<CompletedLesson>)
@Query("SELECT * FROM CompletedLesson WHERE diary_id = :diaryId AND student_id = :studentId AND date >= :from AND date <= :end")
fun loadAll(diaryId: Int, studentId: Int, from: LocalDate, end: LocalDate): Maybe<List<CompletedLesson>>
}

View File

@ -0,0 +1,40 @@
package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import org.threeten.bp.LocalDate
import java.io.Serializable
@Entity(tableName = "CompletedLesson")
data class CompletedLesson(
@ColumnInfo(name = "student_id")
var studentId: Int,
@ColumnInfo(name = "diary_id")
var diaryId: Int,
var date: LocalDate,
var number: Int,
var subject: String,
var topic: String,
var teacher: String,
@ColumnInfo(name = "teacher_symbol")
var teacherSymbol: String,
var substitution: String,
var absence: String,
var resources: String
) : Serializable {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}

View File

@ -0,0 +1,23 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration3 : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE CompletedLesson (" +
"id INTEGER NOT NULL PRIMARY KEY, " +
"student_id INTEGER NOT NULL, " +
"diary_id INTEGER NOT NULL, " +
"date INTEGER NOT NULL, " +
"number INTEGER NOT NULL, " +
"subject TEXT NOT NULL, " +
"topic TEXT NOT NULL, " +
"teacher TEXT NOT NULL, " +
"teacher_symbol TEXT NOT NULL, " +
"substitution TEXT NOT NULL, " +
"absence TEXT NOT NULL, " +
"resources TEXT NOT NULL)")
}
}

View File

@ -0,0 +1,45 @@
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.CompletedLesson
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.repositories.local.CompletedLessonsLocal
import io.github.wulkanowy.data.repositories.remote.CompletedLessonsRemote
import io.github.wulkanowy.utils.friday
import io.github.wulkanowy.utils.monday
import io.reactivex.Single
import org.threeten.bp.LocalDate
import java.net.UnknownHostException
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class CompletedLessonsRepository @Inject constructor(
private val settings: InternetObservingSettings,
private val local: CompletedLessonsLocal,
private val remote: CompletedLessonsRemote
) {
fun getCompletedLessons(semester: Semester, startDate: LocalDate, endDate: LocalDate, forceRefresh: Boolean = false): Single<List<CompletedLesson>> {
return Single.fromCallable { startDate.monday to endDate.friday }
.flatMap { dates ->
local.getCompletedLessons(semester, dates.first, dates.second).filter { !forceRefresh }
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap {
if (it) remote.getCompletedLessons(semester, dates.first, dates.second)
else Single.error(UnknownHostException())
}.flatMap { new ->
local.getCompletedLessons(semester, dates.first, dates.second)
.toSingle(emptyList())
.doOnSuccess { old ->
local.deleteCompleteLessons(old - new)
local.saveCompletedLessons(new - old)
}
}.flatMap {
local.getCompletedLessons(semester, dates.first, dates.second)
.toSingle(emptyList())
}).map { list -> list.filter { it.date in startDate..endDate } }
}
}
}

View File

@ -0,0 +1,25 @@
package io.github.wulkanowy.data.repositories.local
import io.github.wulkanowy.data.db.dao.CompletedLessonsDao
import io.github.wulkanowy.data.db.entities.CompletedLesson
import io.github.wulkanowy.data.db.entities.Semester
import io.reactivex.Maybe
import org.threeten.bp.LocalDate
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class CompletedLessonsLocal @Inject constructor(private val completedLessonsDb: CompletedLessonsDao) {
fun getCompletedLessons(semester: Semester, start: LocalDate, end: LocalDate): Maybe<List<CompletedLesson>> {
return completedLessonsDb.loadAll(semester.diaryId, semester.studentId, start, end).filter { !it.isEmpty() }
}
fun saveCompletedLessons(completedLessons: List<CompletedLesson>) {
completedLessonsDb.insertAll(completedLessons)
}
fun deleteCompleteLessons(completedLessons: List<CompletedLesson>) {
completedLessonsDb.deleteAll(completedLessons)
}
}

View File

@ -0,0 +1,37 @@
package io.github.wulkanowy.data.repositories.remote
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.api.toLocalDate
import io.github.wulkanowy.data.db.entities.CompletedLesson
import io.github.wulkanowy.data.db.entities.Semester
import io.reactivex.Single
import org.threeten.bp.LocalDate
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class CompletedLessonsRemote @Inject constructor(private val api: Api) {
fun getCompletedLessons(semester: Semester, startDate: LocalDate, endDate: LocalDate): Single<List<CompletedLesson>> {
return Single.just(api.apply { diaryId = semester.diaryId })
.flatMap { it.getCompletedLessons(startDate, endDate) }
.map { lessons ->
lessons.map {
it.absence
CompletedLesson(
studentId = semester.studentId,
diaryId = semester.diaryId,
date = it.date.toLocalDate(),
number = it.number,
subject = it.subject,
topic = it.topic,
teacher = it.teacher,
teacherSymbol = it.teacherSymbol,
substitution = it.substitution,
absence = it.absence,
resources = it.resources
)
}
}
}
}

View File

@ -4,6 +4,7 @@ import com.firebase.jobdispatcher.JobParameters
import com.firebase.jobdispatcher.SimpleJobService
import dagger.android.AndroidInjection
import io.github.wulkanowy.data.repositories.AttendanceRepository
import io.github.wulkanowy.data.repositories.CompletedLessonsRepository
import io.github.wulkanowy.data.repositories.ExamRepository
import io.github.wulkanowy.data.repositories.GradeRepository
import io.github.wulkanowy.data.repositories.GradeSummaryRepository
@ -64,6 +65,9 @@ class SyncWorker : SimpleJobService() {
@Inject
lateinit var luckyNumber: LuckyNumberRepository
@Inject
lateinit var completedLessons: CompletedLessonsRepository
@Inject
lateinit var prefRepository: PreferencesRepository
@ -105,7 +109,8 @@ class SyncWorker : SimpleJobService() {
note.getNotes(it.first, true, notify).ignoreElement(),
homework.getHomework(it.first, LocalDate.now(), true).ignoreElement(),
homework.getHomework(it.first, LocalDate.now().plusDays(1), true).ignoreElement(),
luckyNumber.getLuckyNumber(it.first, true, notify).ignoreElement()
luckyNumber.getLuckyNumber(it.first, true, notify).ignoreElement(),
completedLessons.getCompletedLessons(it.first, start, end, true).ignoreElement()
)
)
}

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy.ui.base
import android.content.res.Resources
import com.readystatesoftware.chuck.api.ChuckCollector
import io.github.wulkanowy.R
import io.github.wulkanowy.api.interceptor.FeatureDisabledException
import io.github.wulkanowy.api.interceptor.ServiceUnavailableException
import io.github.wulkanowy.api.login.NotLoggedInException
import timber.log.Timber
@ -26,6 +27,7 @@ open class ErrorHandler @Inject constructor(protected val resources: Resources,
is SocketTimeoutException -> resources.getString(R.string.error_timeout)
is NotLoggedInException -> resources.getString(R.string.error_login_failed)
is ServiceUnavailableException -> resources.getString(R.string.error_service_unavailable)
is FeatureDisabledException -> resources.getString(R.string.error_feature_disabled)
else -> resources.getString(R.string.error_unknown)
}), error)
}

View File

@ -6,7 +6,10 @@ import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.security.ScramblerException
import javax.inject.Inject
class SessionErrorHandler @Inject constructor(resources: Resources, chuckCollector: ChuckCollector) : ErrorHandler(resources, chuckCollector) {
open class SessionErrorHandler @Inject constructor(
resources: Resources,
chuckCollector: ChuckCollector
) : ErrorHandler(resources, chuckCollector) {
var onDecryptionFail: () -> Unit = {}

View File

@ -8,7 +8,10 @@ import io.github.wulkanowy.api.login.BadCredentialsException
import io.github.wulkanowy.ui.base.ErrorHandler
import javax.inject.Inject
class LoginErrorHandler @Inject constructor(resources: Resources, chuckCollector: ChuckCollector) : ErrorHandler(resources, chuckCollector) {
class LoginErrorHandler @Inject constructor(
resources: Resources,
chuckCollector: ChuckCollector
) : ErrorHandler(resources, chuckCollector) {
var onBadCredentials: () -> Unit = {}

View File

@ -24,6 +24,7 @@ import io.github.wulkanowy.ui.modules.more.MoreFragment
import io.github.wulkanowy.ui.modules.note.NoteFragment
import io.github.wulkanowy.ui.modules.settings.SettingsFragment
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
import io.github.wulkanowy.ui.modules.timetable.completed.CompletedLessonsFragment
@Module
abstract class MainModule {
@ -93,5 +94,9 @@ abstract class MainModule {
@PerFragment
@ContributesAndroidInjector
abstract fun bindsAccountDialog(): AccountDialog
abstract fun bindAccountDialog(): AccountDialog
@PerFragment
@ContributesAndroidInjector
abstract fun bindCompletedLessonsFragment(): CompletedLessonsFragment
}

View File

@ -2,6 +2,9 @@ package io.github.wulkanowy.ui.modules.timetable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter
@ -12,6 +15,7 @@ import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.ui.base.session.BaseSessionFragment
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.timetable.completed.CompletedLessonsFragment
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_timetable.*
import javax.inject.Inject
@ -39,6 +43,14 @@ class TimetableFragment : BaseSessionFragment(), TimetableView, MainView.MainChi
override val isViewEmpty: Boolean
get() = timetableAdapter.isEmpty
override val currentStackSize: Int?
get() = (activity as? MainActivity)?.currentStackSize
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_timetable, container, false)
}
@ -63,6 +75,15 @@ class TimetableFragment : BaseSessionFragment(), TimetableView, MainView.MainChi
timetableNextButton.setOnClickListener { presenter.onNextDay() }
}
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
inflater?.inflate(R.menu.action_menu_timetable, menu)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
return if (item?.itemId == R.id.timetableMenuCompletedLessons) presenter.onCompletedLessonsSwitchSelected()
else false
}
override fun updateData(data: List<TimetableItem>) {
timetableAdapter.updateDataSet(data, true)
}
@ -87,6 +108,10 @@ class TimetableFragment : BaseSessionFragment(), TimetableView, MainView.MainChi
presenter.onViewReselected()
}
override fun popView() {
(activity as? MainActivity)?.popView()
}
override fun showEmpty(show: Boolean) {
timetableEmpty.visibility = if (show) View.VISIBLE else View.GONE
}
@ -111,6 +136,10 @@ class TimetableFragment : BaseSessionFragment(), TimetableView, MainView.MainChi
(activity as? MainActivity)?.showDialogFragment(TimetableDialog.newInstance(lesson))
}
override fun openCompletedLessonsView() {
(activity as? MainActivity)?.pushView(CompletedLessonsFragment.newInstance())
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putLong(SAVED_DATE_KEY, presenter.currentDate.toEpochDay())

View File

@ -57,12 +57,16 @@ class TimetablePresenter @Inject constructor(
}
fun onViewReselected() {
Timber.i("Exam view is reselected")
now().nextOrSameSchoolDay.also {
if (currentDate != it) {
loadData(it)
reloadView()
} else if (view?.isViewEmpty == false) view?.resetView()
Timber.i("Timetable view is reselected")
view?.also { view ->
if (view.currentStackSize == 1) {
now().nextOrSameSchoolDay.also {
if (currentDate != it) {
loadData(it)
reloadView()
} else if (!view.isViewEmpty) view.resetView()
}
} else view.popView()
}
}
@ -73,6 +77,11 @@ class TimetablePresenter @Inject constructor(
}
}
fun onCompletedLessonsSwitchSelected(): Boolean {
view?.openCompletedLessonsView()
return true
}
private fun loadData(date: LocalDate, forceRefresh: Boolean = false) {
Timber.i("Loading timetable data started")
currentDate = date

View File

@ -9,6 +9,8 @@ interface TimetableView : BaseSessionView {
val isViewEmpty: Boolean
val currentStackSize: Int?
fun initView()
fun updateData(data: List<TimetableItem>)
@ -32,4 +34,8 @@ interface TimetableView : BaseSessionView {
fun showNextButton(show: Boolean)
fun showTimetableDialog(lesson: Timetable)
fun popView()
fun openCompletedLessonsView()
}

View File

@ -0,0 +1,73 @@
package io.github.wulkanowy.ui.modules.timetable.completed
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.CompletedLesson
import kotlinx.android.synthetic.main.dialog_lesson_completed.*
class CompletedLessonDialog : DialogFragment() {
private lateinit var completedLesson: CompletedLesson
companion object {
private const val ARGUMENT_KEY = "Item"
fun newInstance(exam: CompletedLesson): CompletedLessonDialog {
return CompletedLessonDialog().apply {
arguments = Bundle().apply { putSerializable(ARGUMENT_KEY, exam) }
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, 0)
arguments?.run {
completedLesson = getSerializable(CompletedLessonDialog.ARGUMENT_KEY) as CompletedLesson
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.dialog_lesson_completed, container, false)
}
@SuppressLint("SetTextI18n")
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
completedLessonDialogSubject.text = completedLesson.subject
completedLessonDialogTopic.text = completedLesson.topic
completedLessonDialogTeacher.text = completedLesson.teacher
completedLessonDialogAbsence.text = completedLesson.absence
completedLessonDialogChanges.text = completedLesson.substitution
completedLessonDialogResources.text = completedLesson.resources
completedLesson.substitution.let {
if (it.isBlank()) {
completedLessonDialogChangesTitle.visibility = View.GONE
completedLessonDialogChanges.visibility = View.GONE
} else completedLessonDialogChanges.text = it
}
completedLesson.absence.let {
if (it.isBlank()) {
completedLessonDialogAbsenceTitle.visibility = View.GONE
completedLessonDialogAbsence.visibility = View.GONE
} else completedLessonDialogAbsence.text = it
}
completedLesson.resources.let {
if (it.isBlank()) {
completedLessonDialogResourcesTitle.visibility = View.GONE
completedLessonDialogResources.visibility = View.GONE
} else completedLessonDialogResources.text = it
}
completedLessonDialogClose.setOnClickListener { dismiss() }
}
}

View File

@ -0,0 +1,53 @@
package io.github.wulkanowy.ui.modules.timetable.completed
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.CompletedLesson
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_completed_lesson.*
class CompletedLessonItem(val completedLesson: CompletedLesson) : AbstractFlexibleItem<CompletedLessonItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_completed_lesson
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?): CompletedLessonItem.ViewHolder {
return CompletedLessonItem.ViewHolder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: CompletedLessonItem.ViewHolder?, position: Int, payloads: MutableList<Any>?) {
holder?.apply {
completedLessonItemNumber.text = completedLesson.number.toString()
completedLessonItemSubject.text = completedLesson.subject
completedLessonItemTopic.text = completedLesson.topic
completedLessonItemAlert.visibility = if (completedLesson.substitution.isNotEmpty()) VISIBLE else GONE
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CompletedLessonItem
if (completedLesson != other.completedLesson) return false
return true
}
override fun hashCode(): Int {
return completedLesson.hashCode()
}
class ViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?) : FlexibleViewHolder(view, adapter),
LayoutContainer {
override val containerView: View?
get() = contentView
}
}

View File

@ -0,0 +1,27 @@
package io.github.wulkanowy.ui.modules.timetable.completed
import android.content.res.Resources
import com.readystatesoftware.chuck.api.ChuckCollector
import io.github.wulkanowy.api.interceptor.FeatureDisabledException
import io.github.wulkanowy.ui.base.session.SessionErrorHandler
import javax.inject.Inject
class CompletedLessonsErrorHandler @Inject constructor(
resources: Resources,
chuckCollector: ChuckCollector
) : SessionErrorHandler(resources, chuckCollector) {
var onFeatureDisabled: () -> Unit = {}
override fun proceed(error: Throwable) {
when (error) {
is FeatureDisabledException -> onFeatureDisabled()
else -> super.proceed(error)
}
}
override fun clear() {
super.clear()
onFeatureDisabled = {}
}
}

View File

@ -0,0 +1,120 @@
package io.github.wulkanowy.ui.modules.timetable.completed
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
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.data.db.entities.CompletedLesson
import io.github.wulkanowy.ui.base.session.BaseSessionFragment
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_timetable_completed.*
import javax.inject.Inject
class CompletedLessonsFragment : BaseSessionFragment(), CompletedLessonsView, MainView.TitledView {
@Inject
lateinit var presenter: CompletedLessonsPresenter
@Inject
lateinit var completedLessonsAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
companion object {
private const val SAVED_DATE_KEY = "CURRENT_DATE"
fun newInstance() = CompletedLessonsFragment()
}
override val titleStringId: Int
get() = R.string.completed_lessons_title
override val isViewEmpty
get() = completedLessonsAdapter.isEmpty
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_timetable_completed, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
messageContainer = completedLessonsRecycler
presenter.onAttachView(this, savedInstanceState?.getLong(SAVED_DATE_KEY))
}
override fun initView() {
completedLessonsAdapter.run {
setOnItemClickListener { presenter.onCompletedLessonsItemSelected(it) }
}
completedLessonsRecycler.run {
layoutManager = SmoothScrollLinearLayoutManager(context)
adapter = completedLessonsAdapter
}
completedLessonsSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
completedLessonsPreviousButton.setOnClickListener { presenter.onPreviousDay() }
completedLessonsNextButton.setOnClickListener { presenter.onNextDay() }
}
override fun updateData(data: List<CompletedLessonItem>) {
completedLessonsAdapter.updateDataSet(data, true)
}
override fun clearData() {
completedLessonsAdapter.clear()
}
override fun updateNavigationDay(date: String) {
completedLessonsNavDate.text = date
}
override fun hideRefresh() {
completedLessonsSwipe.isRefreshing = false
}
override fun showEmpty(show: Boolean) {
completedLessonsEmpty.visibility = if (show) View.VISIBLE else View.GONE
}
override fun showFeatureDisabled() {
context?.let {
completedLessonsInfo.text = getString(R.string.error_feature_disabled)
completedLessonsInfoImage.setImageDrawable(ContextCompat.getDrawable(it, R.drawable.ic_all_close_circle_24dp))
}
}
override fun showProgress(show: Boolean) {
completedLessonsProgress.visibility = if (show) View.VISIBLE else View.GONE
}
override fun showContent(show: Boolean) {
completedLessonsRecycler.visibility = if (show) View.VISIBLE else View.GONE
}
override fun showPreButton(show: Boolean) {
completedLessonsPreviousButton.visibility = if (show) View.VISIBLE else View.INVISIBLE
}
override fun showNextButton(show: Boolean) {
completedLessonsNextButton.visibility = if (show) View.VISIBLE else View.INVISIBLE
}
override fun showCompletedLessonDialog(completedLesson: CompletedLesson) {
(activity as? MainActivity)?.showDialogFragment(CompletedLessonDialog.newInstance(completedLesson))
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putLong(CompletedLessonsFragment.SAVED_DATE_KEY, presenter.currentDate.toEpochDay())
}
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()
}
}

View File

@ -0,0 +1,116 @@
package io.github.wulkanowy.ui.modules.timetable.completed
import com.google.firebase.analytics.FirebaseAnalytics.Param.START_DATE
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.repositories.CompletedLessonsRepository
import io.github.wulkanowy.data.repositories.SemesterRepository
import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.ui.base.session.BaseSessionPresenter
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.isHolidays
import io.github.wulkanowy.utils.nextOrSameSchoolDay
import io.github.wulkanowy.utils.nextSchoolDay
import io.github.wulkanowy.utils.previousSchoolDay
import io.github.wulkanowy.utils.toFormattedString
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDate.now
import org.threeten.bp.LocalDate.ofEpochDay
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
class CompletedLessonsPresenter @Inject constructor(
private val schedulers: SchedulersProvider,
private val errorHandler: CompletedLessonsErrorHandler,
private val studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository,
private val completedLessonsRepository: CompletedLessonsRepository,
private val analytics: FirebaseAnalyticsHelper
) : BaseSessionPresenter<CompletedLessonsView>(errorHandler) {
lateinit var currentDate: LocalDate
private set
fun onAttachView(view: CompletedLessonsView, date: Long?) {
super.onAttachView(view)
Timber.i("Completed lessons is attached")
view.initView()
loadData(ofEpochDay(date ?: now().nextOrSameSchoolDay.toEpochDay()))
reloadView()
errorHandler.onFeatureDisabled = {
this.view?.showFeatureDisabled()
Timber.i("Completed lessons feature disabled by school")
}
}
fun onPreviousDay() {
loadData(currentDate.previousSchoolDay)
reloadView()
}
fun onNextDay() {
loadData(currentDate.nextSchoolDay)
reloadView()
}
fun onSwipeRefresh() {
Timber.i("Force refreshing the completed lessons")
loadData(currentDate, true)
}
fun onCompletedLessonsItemSelected(item: AbstractFlexibleItem<*>?) {
if (item is CompletedLessonItem) {
Timber.i("Select completed lessons item ${item.completedLesson.id}")
view?.showCompletedLessonDialog(item.completedLesson)
}
}
private fun loadData(date: LocalDate, forceRefresh: Boolean = false) {
Timber.i("Loading completed lessons data started")
currentDate = date
disposable.apply {
clear()
add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getCurrentSemester(it) }
.delay(200, TimeUnit.MILLISECONDS)
.flatMap { completedLessonsRepository.getCompletedLessons(it, currentDate, currentDate, forceRefresh) }
.map { items -> items.map { CompletedLessonItem(it) } }
.map { items -> items.sortedBy { it.completedLesson.number } }
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.doFinally {
view?.run {
hideRefresh()
showProgress(false)
}
}
.subscribe({
Timber.i("Loading completed lessons lessons result: Success")
view?.apply {
updateData(it)
showEmpty(it.isEmpty())
showContent(it.isNotEmpty())
}
analytics.logEvent("load_completed_lessons", "items" to it.size, "force_refresh" to forceRefresh, START_DATE to currentDate.toFormattedString("yyyy-MM-dd"))
}) {
Timber.i("Loading completed lessons result: An exception occurred")
view?.run { showEmpty(isViewEmpty) }
errorHandler.dispatch(it)
})
}
}
private fun reloadView() {
Timber.i("Reload completed lessons view with the date ${currentDate.toFormattedString()}")
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

@ -0,0 +1,33 @@
package io.github.wulkanowy.ui.modules.timetable.completed
import io.github.wulkanowy.data.db.entities.CompletedLesson
import io.github.wulkanowy.ui.base.session.BaseSessionView
interface CompletedLessonsView : BaseSessionView {
val isViewEmpty: Boolean
fun initView()
fun updateData(data: List<CompletedLessonItem>)
fun clearData()
fun updateNavigationDay(date: String)
fun hideRefresh()
fun showEmpty(show: Boolean)
fun showFeatureDisabled()
fun showProgress(show: Boolean)
fun showContent(show: Boolean)
fun showPreButton(show: Boolean)
fun showNextButton(show: Boolean)
fun showCompletedLessonDialog(completedLesson: CompletedLesson)
}

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2C6.47,2 2,6.47 2,12C2,17.53 6.47,22 12,22C17.53,22 22,17.53 22,12C22,6.47 17.53,2 12,2M14.59,8L12,10.59L9.41,8L8,9.41L10.59,12L8,14.59L9.41,16L12,13.41L14.59,16L16,14.59L13.41,12L16,9.41L14.59,8Z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M19,19H5V8H19M19,3H18V1H16V3H8V1H6V3H5C3.89,3 3,3.9 3,5V19A2,2 0,0 0,5 21H19A2,2 0,0 0,21 19V5A2,2 0,0 0,19 3M16.53,11.06L15.47,10L10.59,14.88L8.47,12.76L7.41,13.82L10.59,17L16.53,11.06Z" />
</vector>

View File

@ -0,0 +1,138 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.modules.timetable.completed.CompletedLessonDialog">
<LinearLayout
android:layout_width="300dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<TextView
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/timetable_lesson"
android:textSize="17sp" />
<TextView
android:id="@+id/completedLessonDialogSubject"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:text="@string/all_no_data"
android:textIsSelectable="true"
android:textSize="12sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/completed_lessons_topic"
android:textSize="17sp" />
<TextView
android:id="@+id/completedLessonDialogTopic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:text="@string/all_no_data"
android:textIsSelectable="true"
android:textSize="12sp" />
<TextView
android:id="@+id/completedLessonDialogTeacherTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/all_teacher"
android:textSize="17sp" />
<TextView
android:id="@+id/completedLessonDialogTeacher"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:text="@string/all_no_data"
android:textIsSelectable="true"
android:textSize="12sp" />
<TextView
android:id="@+id/completedLessonDialogChangesTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/timetable_changes"
android:textColor="@color/colorPrimary"
android:textSize="17sp" />
<TextView
android:id="@+id/completedLessonDialogChanges"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:text="@string/all_no_data"
android:textColor="@color/colorPrimary"
android:textIsSelectable="true"
android:textSize="12sp" />
<TextView
android:id="@+id/completedLessonDialogAbsenceTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/completed_lessons_absence"
android:textColor="@color/colorPrimary"
android:textSize="17sp" />
<TextView
android:id="@+id/completedLessonDialogAbsence"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:text="@string/all_no_data"
android:textColor="@color/colorPrimary"
android:textIsSelectable="true"
android:textSize="12sp" />
<TextView
android:id="@+id/completedLessonDialogResourcesTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/completed_lessons_resources"
android:textSize="17sp" />
<TextView
android:id="@+id/completedLessonDialogResources"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:autoLink="web"
android:text="@string/all_no_data"
android:textIsSelectable="true"
android:textSize="12sp" />
<Button
android:id="@+id/completedLessonDialogClose"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="15dp"
android:text="@string/all_close"
android:textAllCaps="true"
android:textSize="15sp" />
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,99 @@
<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"
tools:context=".ui.modules.timetable.completed.CompletedLessonsFragment">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="50dp">
<ProgressBar
android:id="@+id/completedLessonsProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true" />
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/completedLessonsSwipe"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/completedLessonsRecycler"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<LinearLayout
android:id="@+id/completedLessonsEmpty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="10dp"
android:visibility="gone">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="100dp"
android:minHeight="100dp"
android:id="@+id/completedLessonsInfoImage"
app:srcCompat="@drawable/ic_menu_main_lessons_completed_24dp"
app:tint="?android:attr/textColorPrimary"
tools:ignore="contentDescription" />
<TextView
android:id="@+id/completedLessonsInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center"
android:text="@string/completed_lessons_no_items"
android:textSize="20sp" />
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_gravity="bottom"
android:background="?android:attr/windowBackground"
android:elevation="10dp"
android:orientation="horizontal">
<Button
android:id="@+id/completedLessonsPreviousButton"
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/all_prev"
android:textAlignment="gravity" />
<TextView
android:id="@+id/completedLessonsNavDate"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/app_name" />
<Button
android:id="@+id/completedLessonsNextButton"
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/all_next"
android:textAlignment="gravity" />
</LinearLayout>
</FrameLayout>

View File

@ -0,0 +1,72 @@
<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:id="@+id/completedLesson_subitem_container"
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"
android:paddingTop="7dp"
android:paddingEnd="12dp"
android:paddingRight="12dp"
android:paddingBottom="7dp"
tools:context=".ui.modules.timetable.completed.CompletedLessonItem">
<TextView
android:id="@+id/completedLessonItemNumber"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_centerVertical="true"
android:gravity="center"
android:maxLength="2"
android:textSize="32sp"
tools:ignore="all"
tools:text="1" />
<TextView
android:id="@+id/completedLessonItemSubject"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:layout_marginEnd="40dp"
android:layout_marginRight="40dp"
android:layout_toEndOf="@+id/completedLessonItemNumber"
android:layout_toRightOf="@+id/completedLessonItemNumber"
android:ellipsize="end"
android:maxLines="1"
android:textSize="17sp"
tools:ignore="RelativeOverlap"
tools:text="@tools:sample/lorem" />
<TextView
android:id="@+id/completedLessonItemTopic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignStart="@id/completedLessonItemSubject"
android:layout_alignLeft="@id/completedLessonItemSubject"
android:layout_alignEnd="@+id/completedLessonItemAlert"
android:layout_alignRight="@+id/completedLessonItemAlert"
android:layout_alignBottom="@+id/completedLessonItemNumber"
android:layout_marginEnd="40dp"
android:layout_marginRight="40dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:attr/android:textColorSecondary"
android:textSize="12sp"
tools:text="@tools:sample/lorem" />
<ImageView
android:id="@+id/completedLessonItemAlert"
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_timetable_swap_30dp"
tools:ignore="contentDescription" />
</RelativeLayout>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/timetableMenuCompletedLessons"
android:icon="@drawable/ic_menu_main_lessons_completed_24dp"
android:orderInCategory="1"
android:title="@string/completed_lessons_button"
app:showAsAction="ifRoom" />
</menu>

View File

@ -102,6 +102,13 @@
<string name="timetable_changes">Zmiany</string>
<string name="timetable_no_items">Brak lekcji w tym dniu</string>
<!--CompletedLesson-->
<string name="completed_lessons_title">Lekcje zrealizowane</string>
<string name="completed_lessons_button">Zobacz lekcje zrealizowane</string>
<string name="completed_lessons_no_items">Brak informacji o lekcjach zrealizowanych</string>
<string name="completed_lessons_topic">Temat</string>
<string name="completed_lessons_absence">Nieobecność</string>
<string name="completed_lessons_resources">Zasoby</string>
<!--Attendance-->
<string name="attendance_summary_button">Podsumowanie frekwencji</string>
@ -263,4 +270,5 @@
<string name="error_login_failed">Logowanie nie powiodło się. Spróbuj ponownie lub zrestartuj aplikację</string>
<string name="error_service_unavailable">Dziennik jest niedostępny. Spróbuj ponownie później</string>
<string name="error_unknown">Wystąpił nieoczekiwany błąd</string>
<string name="error_feature_disabled">Funkcja wyłączona przez szkołę</string>
</resources>

View File

@ -93,6 +93,13 @@
<string name="timetable_changes">Changes</string>
<string name="timetable_no_items">No lessons this day</string>
<!--Completed lessons-->
<string name="completed_lessons_title">Completed lessons</string>
<string name="completed_lessons_button">Show completed lessons</string>
<string name="completed_lessons_no_items">No info about completed lessons</string>
<string name="completed_lessons_topic">Topic</string>
<string name="completed_lessons_absence">Absence</string>
<string name="completed_lessons_resources">Resources</string>
<!--Attendance-->
<string name="attendance_summary_button">Attendance summary</string>
@ -246,4 +253,5 @@
<string name="error_login_failed">Login is failed. Try again or restart the app</string>
<string name="error_service_unavailable">The log is not available. Try again later</string>
<string name="error_unknown">An unexpected error occurred</string>
<string name="error_feature_disabled">Feature disabled by your school</string>
</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.timetable.CompletedLesson
import io.github.wulkanowy.data.db.entities.Semester
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.SpyK
import io.reactivex.Single
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.threeten.bp.LocalDate
import java.sql.Date
class CompletedLessonsRemoteTest {
@SpyK
private var mockApi = Api()
@MockK
private lateinit var semesterMock: Semester
@Before
fun initApi() {
MockKAnnotations.init(this)
}
@Test
fun getCompletedLessonsTest() {
every {
mockApi.getCompletedLessons(
LocalDate.of(2018, 9, 10),
LocalDate.of(2018, 9, 15)
)
} returns Single.just(listOf(
getCompletedLesson("2018-09-10"),
getCompletedLesson("2018-09-17")
))
every { mockApi.diaryId } returns 1
every { semesterMock.studentId } returns 1
every { semesterMock.diaryId } returns 1
val completed = CompletedLessonsRemote(mockApi).getCompletedLessons(semesterMock,
LocalDate.of(2018, 9, 10),
LocalDate.of(2018, 9, 15)
).blockingGet()
Assert.assertEquals(2, completed.size)
}
private fun getCompletedLesson(dateString: String): CompletedLesson {
return CompletedLesson().apply { date = Date.valueOf(dateString) }
}
}