Add teachers (#489)

This commit is contained in:
Dominik Korsa
2019-09-29 18:09:53 +02:00
committed by Mikołaj Pich
parent ed7996299e
commit a45bc0eef6
26 changed files with 2054 additions and 5 deletions

View File

@ -136,4 +136,8 @@ internal class RepositoryModule {
@Singleton
@Provides
fun provideMobileDevicesDao(database: AppDatabase) = database.mobileDeviceDao
@Singleton
@Provides
fun provideTeacherDao(database: AppDatabase) = database.teacherDao
}

View File

@ -23,6 +23,7 @@ import io.github.wulkanowy.data.db.dao.ReportingUnitDao
import io.github.wulkanowy.data.db.dao.SemesterDao
import io.github.wulkanowy.data.db.dao.StudentDao
import io.github.wulkanowy.data.db.dao.SubjectDao
import io.github.wulkanowy.data.db.dao.TeacherDao
import io.github.wulkanowy.data.db.dao.TimetableDao
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.AttendanceSummary
@ -41,6 +42,7 @@ import io.github.wulkanowy.data.db.entities.ReportingUnit
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.Teacher
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.db.migrations.Migration10
import io.github.wulkanowy.data.db.migrations.Migration11
@ -48,6 +50,7 @@ import io.github.wulkanowy.data.db.migrations.Migration12
import io.github.wulkanowy.data.db.migrations.Migration13
import io.github.wulkanowy.data.db.migrations.Migration14
import io.github.wulkanowy.data.db.migrations.Migration15
import io.github.wulkanowy.data.db.migrations.Migration16
import io.github.wulkanowy.data.db.migrations.Migration2
import io.github.wulkanowy.data.db.migrations.Migration3
import io.github.wulkanowy.data.db.migrations.Migration4
@ -78,7 +81,8 @@ import javax.inject.Singleton
CompletedLesson::class,
ReportingUnit::class,
Recipient::class,
MobileDevice::class
MobileDevice::class,
Teacher::class
],
version = AppDatabase.VERSION_SCHEMA,
exportSchema = true
@ -87,7 +91,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() {
companion object {
const val VERSION_SCHEMA = 15
const val VERSION_SCHEMA = 16
fun newInstance(context: Context): AppDatabase {
return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
@ -108,7 +112,8 @@ abstract class AppDatabase : RoomDatabase() {
Migration12(),
Migration13(),
Migration14(),
Migration15()
Migration15(),
Migration16()
)
.build()
}
@ -149,4 +154,6 @@ abstract class AppDatabase : RoomDatabase() {
abstract val recipientDao: RecipientDao
abstract val mobileDeviceDao: MobileDeviceDao
abstract val teacherDao: TeacherDao
}

View File

@ -0,0 +1,23 @@
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.Teacher
import io.reactivex.Maybe
import javax.inject.Singleton
@Singleton
@Dao
interface TeacherDao {
@Insert
fun insertAll(devices: List<Teacher>)
@Delete
fun deleteAll(devices: List<Teacher>)
@Query("SELECT * FROM Teachers WHERE student_id = :studentId AND class_id = :classId")
fun loadAll(studentId: Int, classId: Int): Maybe<List<Teacher>>
}

View File

@ -0,0 +1,27 @@
package io.github.wulkanowy.data.db.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable
@Entity(tableName = "Teachers")
data class Teacher(
@ColumnInfo(name = "student_id")
val studentId: Int,
@ColumnInfo(name = "class_id")
val classId: Int,
val subject: String,
val name: String,
@ColumnInfo(name = "short_name")
val shortName: String
) : Serializable {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}

View File

@ -0,0 +1,20 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration16 : Migration(15, 16) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("""
CREATE TABLE IF NOT EXISTS Teachers (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
student_id INTEGER NOT NULL,
class_id INTEGER NOT NULL,
subject TEXT NOT NULL,
name TEXT NOT NULL,
short_name TEXT NOT NULL
)
""")
}
}

View File

@ -0,0 +1,21 @@
package io.github.wulkanowy.data.repositories.teacher
import io.github.wulkanowy.data.db.dao.TeacherDao
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Teacher
import io.reactivex.Maybe
import javax.inject.Inject
class TeacherLocal @Inject constructor(private val teacherDb: TeacherDao) {
fun saveTeachers(teachers: List<Teacher>) {
teacherDb.insertAll(teachers)
}
fun deleteTeachers(teachers: List<Teacher>) {
teacherDb.deleteAll(teachers)
}
fun getTeachers(semester: Semester): Maybe<List<Teacher>> {
return teacherDb.loadAll(semester.studentId, semester.classId).filter { it.isNotEmpty() }
}
}

View File

@ -0,0 +1,28 @@
package io.github.wulkanowy.data.repositories.teacher
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Teacher
import io.reactivex.Single
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class TeacherRemote @Inject constructor(private val api: Api) {
fun getTeachers(semester: Semester): Single<List<Teacher>> {
return Single.just(api.apply { diaryId = semester.diaryId })
.flatMap { it.getTeachers() }
.map { teachers ->
teachers.map {
Teacher(
studentId = semester.studentId,
name = it.name,
subject = it.subject,
shortName = it.short,
classId = semester.classId
)
}
}
}
}

View File

@ -0,0 +1,33 @@
package io.github.wulkanowy.data.repositories.teacher
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Teacher
import io.github.wulkanowy.utils.uniqueSubtract
import io.reactivex.Single
import java.net.UnknownHostException
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class TeacherRepository @Inject constructor(
private val settings: InternetObservingSettings,
private val local: TeacherLocal,
private val remote: TeacherRemote
) {
fun getTeachers(semester: Semester, forceRefresh: Boolean = false): Single<List<Teacher>> {
return local.getTeachers(semester).filter { !forceRefresh }
.switchIfEmpty(ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap {
if (it) remote.getTeachers(semester)
else Single.error(UnknownHostException())
}.flatMap { new ->
local.getTeachers(semester).toSingle(emptyList())
.doOnSuccess { old ->
local.deleteTeachers(old.uniqueSubtract(new))
local.saveTeachers(new.uniqueSubtract(old))
}
}.flatMap { local.getTeachers(semester).toSingle(emptyList()) })
}
}

View File

@ -21,6 +21,7 @@ import io.github.wulkanowy.services.sync.works.LuckyNumberWork
import io.github.wulkanowy.services.sync.works.MessageWork
import io.github.wulkanowy.services.sync.works.NoteWork
import io.github.wulkanowy.services.sync.works.RecipientWork
import io.github.wulkanowy.services.sync.works.TeacherWork
import io.github.wulkanowy.services.sync.works.TimetableWork
import io.github.wulkanowy.services.sync.works.Work
import io.github.wulkanowy.services.widgets.TimetableWidgetService
@ -75,6 +76,10 @@ abstract class ServicesModule {
@IntoSet
abstract fun provideTimetableWork(work: TimetableWork): Work
@Binds
@IntoSet
abstract fun provideTeacherWork(work: TeacherWork): Work
@Binds
@IntoSet
abstract fun provideLuckyNumberWork(work: LuckyNumberWork): Work

View File

@ -0,0 +1,14 @@
package io.github.wulkanowy.services.sync.works
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.teacher.TeacherRepository
import io.reactivex.Completable
import javax.inject.Inject
class TeacherWork @Inject constructor(private val teacherRepository: TeacherRepository) : Work {
override fun create(student: Student, semester: Semester): Completable {
return teacherRepository.getTeachers(semester, true).ignoreElement()
}
}

View File

@ -27,6 +27,7 @@ import io.github.wulkanowy.ui.modules.mobiledevice.token.MobileDeviceTokenDialog
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.teacher.TeacherFragment
import io.github.wulkanowy.ui.modules.timetable.TimetableFragment
import io.github.wulkanowy.ui.modules.timetable.completed.CompletedLessonsFragment
@ -120,4 +121,8 @@ abstract class MainModule {
@PerFragment
@ContributesAndroidInjector(modules = [LicenseModule::class])
abstract fun bindLicenseFragment(): LicenseFragment
@PerFragment
@ContributesAndroidInjector
abstract fun bindTeacherFragment(): TeacherFragment
}

View File

@ -19,6 +19,7 @@ import io.github.wulkanowy.ui.modules.message.MessageFragment
import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceFragment
import io.github.wulkanowy.ui.modules.note.NoteFragment
import io.github.wulkanowy.ui.modules.settings.SettingsFragment
import io.github.wulkanowy.ui.modules.teacher.TeacherFragment
import io.github.wulkanowy.utils.getCompatDrawable
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_more.*
@ -54,6 +55,9 @@ class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.Mai
override val mobileDevicesRes: Pair<String, Drawable?>?
get() = context?.run { getString(R.string.mobile_devices_title) to getCompatDrawable(R.drawable.ic_more_mobile_devices) }
override val teachersRes: Pair<String, Drawable?>?
get() = context?.run { getString(R.string.teachers_title) to getCompatDrawable((R.drawable.ic_more_teacher)) }
override val settingsRes: Pair<String, Drawable?>?
get() = context?.run { getString(R.string.settings_title) to getCompatDrawable(R.drawable.ic_more_settings) }
@ -106,6 +110,10 @@ class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.Mai
(activity as? MainActivity)?.pushView(MobileDeviceFragment.newInstance())
}
override fun openTeachersView() {
(activity as? MainActivity)?.pushView(TeacherFragment.newInstance())
}
override fun openSettingsView() {
(activity as? MainActivity)?.pushView(SettingsFragment.newInstance())
}

View File

@ -31,6 +31,7 @@ class MorePresenter @Inject constructor(
noteRes?.first -> openNoteView()
luckyNumberRes?.first -> openLuckyNumberView()
mobileDevicesRes?.first -> openMobileDevicesView()
teachersRes?.first -> openTeachersView()
settingsRes?.first -> openSettingsView()
aboutRes?.first -> openAboutView()
}
@ -51,6 +52,7 @@ class MorePresenter @Inject constructor(
noteRes?.let { MoreItem(it.first, it.second) },
luckyNumberRes?.let { MoreItem(it.first, it.second) },
mobileDevicesRes?.let { MoreItem(it.first, it.second) },
teachersRes?.let { MoreItem(it.first, it.second) },
settingsRes?.let { MoreItem(it.first, it.second) },
aboutRes?.let { MoreItem(it.first, it.second) })
)

View File

@ -15,6 +15,8 @@ interface MoreView : BaseView {
val mobileDevicesRes: Pair<String, Drawable?>?
val teachersRes: Pair<String, Drawable?>?
val settingsRes: Pair<String, Drawable?>?
val aboutRes: Pair<String, Drawable?>?
@ -38,4 +40,6 @@ interface MoreView : BaseView {
fun openLuckyNumberView()
fun openMobileDevicesView()
fun openTeachersView()
}

View File

@ -0,0 +1,93 @@
package io.github.wulkanowy.ui.modules.teacher
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.FlexibleItemDecoration
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainView
import kotlinx.android.synthetic.main.fragment_teacher.*
import javax.inject.Inject
class TeacherFragment : BaseFragment(), TeacherView, MainView.TitledView {
@Inject
lateinit var presenter: TeacherPresenter
@Inject
lateinit var teacherAdapter: FlexibleAdapter<AbstractFlexibleItem<*>>
companion object {
fun newInstance() = TeacherFragment()
}
override val titleStringId: Int
get() = R.string.teachers_title
override val noSubjectString get() = getString(R.string.teacher_no_subject)
override val isViewEmpty: Boolean
get() = teacherAdapter.isEmpty
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_teacher, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
presenter.onAttachView(this)
}
override fun initView() {
teacherRecycler.run {
layoutManager = SmoothScrollLinearLayoutManager(context)
adapter = teacherAdapter
addItemDecoration(FlexibleItemDecoration(context)
.withDefaultDivider()
.withDrawDividerOnLastItem(false)
)
}
teacherSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
}
override fun updateData(data: List<TeacherItem>) {
teacherAdapter.updateDataSet(data, true)
}
override fun updateItem(item: AbstractFlexibleItem<*>) {
teacherAdapter.updateItem(item)
}
override fun clearData() {
teacherAdapter.clear()
}
override fun showEmpty(show: Boolean) {
teacherEmpty.visibility = if (show) View.VISIBLE else View.GONE
}
override fun showProgress(show: Boolean) {
teacherProgress.visibility = if (show) View.VISIBLE else View.GONE
}
override fun enableSwipe(enable: Boolean) {
teacherSwipe.isEnabled = enable
}
override fun showContent(show: Boolean) {
teacherRecycler.visibility = if (show) View.VISIBLE else View.GONE
}
override fun hideRefresh() {
teacherSwipe.isRefreshing = false
}
override fun onDestroyView() {
presenter.onDetachView()
super.onDestroyView()
}
}

View File

@ -0,0 +1,59 @@
package io.github.wulkanowy.ui.modules.teacher
import android.annotation.SuppressLint
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.Teacher
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_teacher.*
class TeacherItem(val teacher: Teacher, private val noSubjectText: String) : AbstractFlexibleItem<TeacherItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_teacher
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): TeacherItem.ViewHolder {
return TeacherItem.ViewHolder(view, adapter)
}
@SuppressLint("SetTextI18n")
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: TeacherItem.ViewHolder, position: Int, payloads: MutableList<Any>?) {
holder.apply {
teacherItemName.text = teacher.name
teacherItemSubject.text = if (teacher.subject.isNotBlank()) teacher.subject else noSubjectText
if (teacher.shortName.isNotBlank()) {
teacherItemShortName.visibility = VISIBLE
teacherItemShortName.text = "[${teacher.shortName}]"
} else {
teacherItemShortName.visibility = GONE
}
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as TeacherItem
if (teacher != other.teacher) return false
if (teacher.id != other.teacher.id) return false
return true
}
override fun hashCode(): Int {
var result = teacher.hashCode()
result = 31 * result + teacher.id.toInt()
return result
}
class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = contentView
}
}

View File

@ -0,0 +1,62 @@
package io.github.wulkanowy.ui.modules.teacher
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.data.repositories.teacher.TeacherRepository
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import timber.log.Timber
import javax.inject.Inject
class TeacherPresenter @Inject constructor(
schedulers: SchedulersProvider,
errorHandler: ErrorHandler,
studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository,
private val teacherRepository: TeacherRepository,
private val analytics: FirebaseAnalyticsHelper
) : BasePresenter<TeacherView>(errorHandler, studentRepository, schedulers) {
override fun onAttachView(view: TeacherView) {
super.onAttachView(view)
view.initView()
Timber.i("Teacher view was initialized")
loadData()
}
fun onSwipeRefresh() {
loadData(true)
}
private fun loadData(forceRefresh: Boolean = false) {
Timber.i("Loading teachers data started")
disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getCurrentSemester(it) }
.flatMap { teacherRepository.getTeachers(it, forceRefresh) }
.map { it.filter { teacher -> teacher.name.isNotBlank() } }
.map { items -> items.map { TeacherItem(it, view?.noSubjectString.orEmpty()) } }
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
.doFinally {
view?.run {
hideRefresh()
showProgress(false)
enableSwipe(true)
}
}.subscribe({
Timber.i("Loading teachers result: Success")
view?.run {
updateData(it)
showContent(it.isNotEmpty())
showEmpty(it.isEmpty())
}
analytics.logEvent("load_teachers", "items" to it.size, "force_refresh" to forceRefresh)
}) {
Timber.i("Loading teachers result: An exception occurred")
errorHandler.dispatch(it)
})
}
}

View File

@ -0,0 +1,29 @@
package io.github.wulkanowy.ui.modules.teacher
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.ui.base.BaseView
interface TeacherView : BaseView {
val isViewEmpty: Boolean
val noSubjectString: String
fun initView()
fun updateData(data: List<TeacherItem>)
fun updateItem(item: AbstractFlexibleItem<*>)
fun hideRefresh()
fun clearData()
fun showProgress(show: Boolean)
fun enableSwipe(enable: Boolean)
fun showContent(show: Boolean)
fun showEmpty(show: Boolean)
}