1
0

Refactor attendance module (#161)

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,24 @@
package io.github.wulkanowy.data.repositories.local
import io.github.wulkanowy.data.db.dao.AttendanceDao
import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.db.entities.Semester
import io.reactivex.Maybe
import org.threeten.bp.LocalDate
import javax.inject.Inject
class AttendanceLocal @Inject constructor(private val attendanceDb: AttendanceDao) {
fun getAttendance(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe<List<Attendance>> {
return attendanceDb.getExams(semester.diaryId, semester.studentId, startDate, endDate)
.filter { !it.isEmpty() }
}
fun saveAttendance(attendance: List<Attendance>) {
attendanceDb.insertAll(attendance)
}
fun deleteAttendance(attendance: List<Attendance>) {
attendanceDb.deleteAll(attendance)
}
}

View File

@ -12,10 +12,9 @@ import javax.inject.Inject
class ExamLocal @Inject constructor(private val examDb: ExamDao) {
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 getExams(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe<List<Exam>> {
return examDb.getExams(semester.diaryId, semester.studentId, startDate, endDate)
.filter { !it.isEmpty() }
}
fun saveExams(exams: List<Exam>) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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