1
0
mirror of https://github.com/wulkanowy/wulkanowy.git synced 2025-02-22 15:14:44 +01:00

Remove flexible adapter (#781)

This commit is contained in:
Mikołaj Pich 2020-05-01 17:38:19 +02:00 committed by GitHub
parent a1f864b35e
commit 4a3b746d48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
147 changed files with 2231 additions and 2864 deletions

View File

@ -80,6 +80,10 @@ android {
} }
} }
viewBinding {
enabled = true
}
lintOptions { lintOptions {
disable 'HardwareIds' disable 'HardwareIds'
} }
@ -118,7 +122,6 @@ ext {
work_manager = "2.3.4" work_manager = "2.3.4"
room = "2.2.5" room = "2.2.5"
dagger = "2.27" dagger = "2.27"
// don't update https://github.com/ChuckerTeam/chucker/issues/242
chucker = "3.2.0" chucker = "3.2.0"
mockk = "1.9.2" mockk = "1.9.2"
} }
@ -167,13 +170,11 @@ dependencies {
implementation "com.squareup.inject:assisted-inject-annotations-dagger2:0.5.2" implementation "com.squareup.inject:assisted-inject-annotations-dagger2:0.5.2"
kapt "com.squareup.inject:assisted-inject-processor-dagger2:0.5.2" kapt "com.squareup.inject:assisted-inject-processor-dagger2:0.5.2"
implementation "eu.davidea:flexible-adapter:5.1.0"
implementation "eu.davidea:flexible-adapter-ui:1.0.0"
implementation "com.aurelhubert:ahbottomnavigation:2.3.4" implementation "com.aurelhubert:ahbottomnavigation:2.3.4"
implementation "com.ncapdevi:frag-nav:3.3.0" implementation "com.ncapdevi:frag-nav:3.3.0"
implementation "com.github.YarikSOffice:lingver:1.2.1" implementation "com.github.YarikSOffice:lingver:1.2.2"
implementation "com.github.pwittchen:reactivenetwork-rx2:3.0.6" implementation "com.github.pwittchen:reactivenetwork-rx2:3.0.8"
implementation "io.reactivex.rxjava2:rxandroid:2.1.1" implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
implementation "io.reactivex.rxjava2:rxjava:2.2.19" implementation "io.reactivex.rxjava2:rxjava:2.2.19"

View File

@ -10,8 +10,6 @@ import com.jakewharton.threetenabp.AndroidThreeTen
import com.yariksoffice.lingver.Lingver import com.yariksoffice.lingver.Lingver
import dagger.android.AndroidInjector import dagger.android.AndroidInjector
import dagger.android.support.DaggerApplication import dagger.android.support.DaggerApplication
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.utils.Log
import fr.bipi.tressence.file.FileLoggerTree import fr.bipi.tressence.file.FileLoggerTree
import io.github.wulkanowy.di.DaggerAppComponent import io.github.wulkanowy.di.DaggerAppComponent
import io.github.wulkanowy.services.sync.SyncWorkerFactory import io.github.wulkanowy.services.sync.SyncWorkerFactory
@ -57,7 +55,6 @@ class WulkanowyApp : DaggerApplication(), Configuration.Provider {
private fun initLogging() { private fun initLogging() {
if (appInfo.isDebug) { if (appInfo.isDebug) {
FlexibleAdapter.enableLogs(Log.Level.DEBUG)
Timber.plant(DebugLogTree()) Timber.plant(DebugLogTree())
Timber.plant(FileLoggerTree.Builder() Timber.plant(FileLoggerTree.Builder()
.withFileName("wulkanowy.%g.log") .withFileName("wulkanowy.%g.log")

View File

@ -1,3 +0,0 @@
package io.github.wulkanowy.data.pojos
class AppCreator(val displayName: String, val githubUsername: String)

View File

@ -0,0 +1,3 @@
package io.github.wulkanowy.data.pojos
class Contributor(val displayName: String, val githubUsername: String)

View File

@ -2,18 +2,18 @@ package io.github.wulkanowy.data.repositories.appcreator
import android.content.res.AssetManager import android.content.res.AssetManager
import com.google.gson.Gson import com.google.gson.Gson
import io.github.wulkanowy.data.pojos.AppCreator import io.github.wulkanowy.data.pojos.Contributor
import io.reactivex.Single import io.reactivex.Single
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class AppCreatorRepository @Inject constructor(private val assets: AssetManager) { class AppCreatorRepository @Inject constructor(private val assets: AssetManager) {
fun getAppCreators(): Single<List<AppCreator>> { fun getAppCreators(): Single<List<Contributor>> {
return Single.fromCallable<List<AppCreator>> { return Single.fromCallable<List<Contributor>> {
Gson().fromJson( Gson().fromJson(
assets.open("contributors.json").bufferedReader().use { it.readText() }, assets.open("contributors.json").bufferedReader().use { it.readText() },
Array<AppCreator>::class.java Array<Contributor>::class.java
).toList() ).toList()
} }
} }

View File

@ -5,8 +5,6 @@ import android.content.Context
import com.yariksoffice.lingver.Lingver import com.yariksoffice.lingver.Lingver
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.WulkanowyApp import io.github.wulkanowy.WulkanowyApp
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
@ -23,9 +21,6 @@ internal class AppModule {
@Provides @Provides
fun provideSchedulersProvider() = SchedulersProvider() fun provideSchedulersProvider() = SchedulersProvider()
@Provides
fun provideFlexibleAdapter() = FlexibleAdapter<AbstractFlexibleItem<*>>(null, null, true)
@Singleton @Singleton
@Provides @Provides
fun provideAppWidgetManager(context: Context): AppWidgetManager = AppWidgetManager.getInstance(context) fun provideAppWidgetManager(context: Context): AppWidgetManager = AppWidgetManager.getInstance(context)

View File

@ -0,0 +1,58 @@
package io.github.wulkanowy.ui.base
import android.util.DisplayMetrics
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.LinearSmoothScroller
import androidx.recyclerview.widget.RecyclerView
import kotlin.math.max
import kotlin.math.min
abstract class BaseExpandableAdapter<T : RecyclerView.ViewHolder> : RecyclerView.Adapter<T>() {
companion object {
private const val MILLISECONDS_PER_INCH = 100f
private const val AUTO_SCROLL_DELAY = 150L
}
private var recyclerView: RecyclerView? = null
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
this.recyclerView = recyclerView
}
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
super.onDetachedFromRecyclerView(recyclerView)
this.recyclerView = null
}
// original: https://github.com/davideas/FlexibleAdapter/blob/5.1.0/flexible-adapter/src/main/java/eu/davidea/flexibleadapter/FlexibleAdapter.java#L4984-L5011
protected fun scrollToHeaderWithSubItems(position: Int, subItemsCount: Int) {
val layoutManager = recyclerView!!.layoutManager as LinearLayoutManager
val firstVisibleItem = layoutManager.findFirstCompletelyVisibleItemPosition()
val lastVisibleItem = layoutManager.findLastCompletelyVisibleItemPosition()
val itemsToShow = position + subItemsCount - lastVisibleItem
if (itemsToShow > 0) {
val scrollMax: Int = position - firstVisibleItem
val scrollMin = max(0, position + subItemsCount - lastVisibleItem)
val scrollBy = min(scrollMax, scrollMin)
val scrollTo = firstVisibleItem + scrollBy
scrollToPosition(scrollTo)
} else if (position < firstVisibleItem) {
scrollToPosition(position)
}
}
private fun scrollToPosition(position: Int) {
recyclerView?.run {
postDelayed({
layoutManager?.startSmoothScroll(object : LinearSmoothScroller(context) {
override fun getVerticalSnapPreference() = SNAP_TO_START
override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics) = MILLISECONDS_PER_INCH / displayMetrics.densityDpi
}.apply {
targetPosition = position
})
}, AUTO_SCROLL_DELAY)
}
}
}

View File

@ -0,0 +1,46 @@
package io.github.wulkanowy.ui.base
import android.annotation.SuppressLint
import android.graphics.PorterDuff
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.databinding.ItemAccountBinding
import io.github.wulkanowy.utils.getThemeAttrColor
import javax.inject.Inject
class WidgetConfigureAdapter @Inject constructor() : RecyclerView.Adapter<WidgetConfigureAdapter.ItemViewHolder>() {
var items = emptyList<Pair<Student, Boolean>>()
var onClickListener: (Student) -> Unit = {}
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
ItemAccountBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val (student, isCurrent) = items[position]
with(holder.binding) {
accountItemName.text = "${student.studentName} ${student.className}"
accountItemSchool.text = student.schoolName
with(accountItemImage) {
val colorImage = if (isCurrent) context.getThemeAttrColor(R.attr.colorPrimary)
else context.getThemeAttrColor(R.attr.colorOnSurface, 153)
setColorFilter(colorImage, PorterDuff.Mode.SRC_IN)
}
root.setOnClickListener { onClickListener(student) }
}
}
class ItemViewHolder(val binding: ItemAccountBinding) : RecyclerView.ViewHolder(binding.root)
}

View File

@ -0,0 +1,72 @@
package io.github.wulkanowy.ui.modules.about
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.content.res.ResourcesCompat
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.databinding.ItemAboutBinding
import io.github.wulkanowy.databinding.ScrollableHeaderAboutBinding
import javax.inject.Inject
class AboutAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private enum class ViewType(val id: Int) {
ITEM_HEADER(1),
ITEM_ELEMENT(2)
}
var items = emptyList<Triple<String, String, Drawable?>>()
var onClickListener: (name: String) -> Unit = {}
override fun getItemCount() = items.size + 1
override fun getItemViewType(position: Int) = when (position) {
0 -> ViewType.ITEM_HEADER.id
else -> ViewType.ITEM_ELEMENT.id
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
ViewType.ITEM_HEADER.id -> HeaderViewHolder(ScrollableHeaderAboutBinding.inflate(inflater, parent, false))
ViewType.ITEM_ELEMENT.id -> ItemViewHolder(ItemAboutBinding.inflate(inflater, parent, false))
else -> throw IllegalStateException()
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is HeaderViewHolder -> bindHeaderViewHolder(holder.binding)
is ItemViewHolder -> bindItemViewHolder(holder.binding, position - 1)
}
}
private fun bindHeaderViewHolder(binding: ScrollableHeaderAboutBinding) {
with(binding.aboutScrollableHeaderIcon) {
setImageDrawable(ResourcesCompat.getDrawableForDensity(
context.resources, context.applicationInfo.icon, 640, null)
)
}
}
private fun bindItemViewHolder(binding: ItemAboutBinding, position: Int) {
val (title, summary, image) = items[position]
with(binding) {
aboutItemImage.setImageDrawable(image)
aboutItemTitle.text = title
aboutItemSummary.text = summary
root.setOnClickListener { onClickListener(title) }
}
}
private class HeaderViewHolder(val binding: ScrollableHeaderAboutBinding) :
RecyclerView.ViewHolder(binding.root)
private class ItemViewHolder(val binding: ItemAboutBinding) :
RecyclerView.ViewHolder(binding.root)
}

View File

@ -5,9 +5,7 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter import androidx.recyclerview.widget.LinearLayoutManager
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.about.contributor.ContributorFragment import io.github.wulkanowy.ui.modules.about.contributor.ContributorFragment
@ -19,7 +17,6 @@ import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.getCompatDrawable import io.github.wulkanowy.utils.getCompatDrawable
import io.github.wulkanowy.utils.openEmailClient import io.github.wulkanowy.utils.openEmailClient
import io.github.wulkanowy.utils.openInternetBrowser import io.github.wulkanowy.utils.openInternetBrowser
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_about.* import kotlinx.android.synthetic.main.fragment_about.*
import javax.inject.Inject import javax.inject.Inject
@ -29,7 +26,7 @@ class AboutFragment : BaseFragment(), AboutView, MainView.TitledView {
lateinit var presenter: AboutPresenter lateinit var presenter: AboutPresenter
@Inject @Inject
lateinit var aboutAdapter: FlexibleAdapter<AbstractFlexibleItem<*>> lateinit var aboutAdapter: AboutAdapter
@Inject @Inject
lateinit var appInfo: AppInfo lateinit var appInfo: AppInfo
@ -90,19 +87,18 @@ class AboutFragment : BaseFragment(), AboutView, MainView.TitledView {
} }
override fun initView() { override fun initView() {
aboutAdapter.setOnItemClickListener(presenter::onItemSelected) aboutAdapter.onClickListener = presenter::onItemSelected
with(aboutRecycler) { with(aboutRecycler) {
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
adapter = aboutAdapter adapter = aboutAdapter
} }
} }
override fun updateData(header: AboutScrollableHeader, items: List<AboutItem>) { override fun updateData(data: List<Triple<String, String, Drawable?>>) {
with(aboutAdapter) { with(aboutAdapter) {
removeAllScrollableHeaders() items = data
addScrollableHeader(header) notifyDataSetChanged()
updateDataSet(items)
} }
} }

View File

@ -1,56 +0,0 @@
package io.github.wulkanowy.ui.modules.about
import android.graphics.drawable.Drawable
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 kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_about.*
class AboutItem(
val title: String,
private val summary: String,
private val image: Drawable?
) : AbstractFlexibleItem<AboutItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_about
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) = ViewHolder(view, adapter)
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
with(holder) {
aboutItemImage.setImageDrawable(image)
aboutItemTitle.text = title
aboutItemSummary.text = summary
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as AboutItem
if (title != other.title) return false
if (summary != other.summary) return false
if (image != other.image) return false
return true
}
override fun hashCode(): Int {
var result = title.hashCode()
result = 31 * result + summary.hashCode()
result = 31 * result + (image?.hashCode() ?: 0)
return result
}
class ViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) : FlexibleViewHolder(view, adapter),
LayoutContainer {
override val containerView: View? get() = contentView
}
}

View File

@ -1,6 +1,5 @@
package io.github.wulkanowy.ui.modules.about package io.github.wulkanowy.ui.modules.about
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
@ -23,10 +22,9 @@ class AboutPresenter @Inject constructor(
loadData() loadData()
} }
fun onItemSelected(item: AbstractFlexibleItem<*>) { fun onItemSelected(name: String) {
if (item !is AboutItem) return
view?.run { view?.run {
when (item.title) { when (name) {
versionRes?.first -> { versionRes?.first -> {
Timber.i("Opening log viewer") Timber.i("Opening log viewer")
openLogViewer() openLogViewer()
@ -73,15 +71,16 @@ class AboutPresenter @Inject constructor(
private fun loadData() { private fun loadData() {
view?.run { view?.run {
updateData(AboutScrollableHeader(), listOfNotNull( updateData(listOfNotNull(
versionRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, versionRes,
creatorsRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, creatorsRes,
feedbackRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, feedbackRes,
faqRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, faqRes,
discordRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, discordRes,
homepageRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, homepageRes,
licensesRes?.let { (title, summary, image) -> AboutItem(title, summary, image) }, licensesRes,
privacyRes?.let { (title, summary, image) -> AboutItem(title, summary, image) })) privacyRes
))
} }
} }
} }

View File

@ -1,41 +0,0 @@
package io.github.wulkanowy.ui.modules.about
import android.view.View
import androidx.core.content.res.ResourcesCompat
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 kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.scrollable_header_about.*
class AboutScrollableHeader : AbstractFlexibleItem<AboutScrollableHeader.ViewHolder>() {
override fun getLayoutRes() = R.layout.scrollable_header_about
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) = ViewHolder(view, adapter)
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
with(holder) {
val context = contentView.context
val drawable = ResourcesCompat.getDrawableForDensity(context.resources, context.applicationInfo.icon, 640, null)
aboutScrollableHeaderIcon.setImageDrawable(drawable)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
return true
}
override fun hashCode() = javaClass.hashCode()
class ViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) : FlexibleViewHolder(view, adapter),
LayoutContainer {
override val containerView: View? get() = contentView
}
}

View File

@ -23,7 +23,7 @@ interface AboutView : BaseView {
fun initView() fun initView()
fun updateData(header: AboutScrollableHeader, items: List<AboutItem>) fun updateData(data: List<Triple<String, String, Drawable?>>)
fun openLogViewer() fun openLogViewer()

View File

@ -0,0 +1,41 @@
package io.github.wulkanowy.ui.modules.about.contributor
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import coil.api.load
import coil.transform.RoundedCornersTransformation
import io.github.wulkanowy.data.pojos.Contributor
import io.github.wulkanowy.databinding.ItemContributorBinding
import javax.inject.Inject
class ContributorAdapter @Inject constructor() :
RecyclerView.Adapter<ContributorAdapter.ItemViewHolder>() {
var items = emptyList<Contributor>()
var onClickListener: (Contributor) -> Unit = {}
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
ItemContributorBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val item = items[position]
with(holder.binding) {
creatorItemName.text = item.displayName
creatorItemAvatar.load("https://github.com/${item.githubUsername}.png") {
transformations(RoundedCornersTransformation(8f))
crossfade(true)
}
root.setOnClickListener { onClickListener(item) }
}
}
class ItemViewHolder(val binding: ItemContributorBinding) :
RecyclerView.ViewHolder(binding.root)
}

View File

@ -6,15 +6,13 @@ import android.view.View
import android.view.View.GONE import android.view.View.GONE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter import androidx.recyclerview.widget.LinearLayoutManager
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.R
import io.github.wulkanowy.data.pojos.Contributor
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.openInternetBrowser import io.github.wulkanowy.utils.openInternetBrowser
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_creator.* import kotlinx.android.synthetic.main.fragment_creator.*
import javax.inject.Inject import javax.inject.Inject
@ -24,7 +22,7 @@ class ContributorFragment : BaseFragment(), ContributorView, MainView.TitledView
lateinit var presenter: ContributorPresenter lateinit var presenter: ContributorPresenter
@Inject @Inject
lateinit var creatorsAdapter: FlexibleAdapter<AbstractFlexibleItem<*>> lateinit var creatorsAdapter: ContributorAdapter
override val titleStringId get() = R.string.contributors_title override val titleStringId get() = R.string.contributors_title
@ -43,18 +41,19 @@ class ContributorFragment : BaseFragment(), ContributorView, MainView.TitledView
override fun initView() { override fun initView() {
with(creatorRecycler) { with(creatorRecycler) {
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
adapter = creatorsAdapter adapter = creatorsAdapter
addItemDecoration(FlexibleItemDecoration(context) addItemDecoration(DividerItemDecoration(context))
.withDefaultDivider()
.withDrawDividerOnLastItem(false))
} }
creatorsAdapter.setOnItemClickListener(presenter::onItemSelected) creatorsAdapter.onClickListener = presenter::onItemSelected
creatorSeeMore.setOnClickListener { presenter.onSeeMoreClick() } creatorSeeMore.setOnClickListener { presenter.onSeeMoreClick() }
} }
override fun updateData(data: List<ContributorItem>) { override fun updateData(data: List<Contributor>) {
creatorsAdapter.updateDataSet(data) with(creatorsAdapter) {
items = data
notifyDataSetChanged()
}
} }
override fun openUserGithubPage(username: String) { override fun openUserGithubPage(username: String) {

View File

@ -1,51 +0,0 @@
package io.github.wulkanowy.ui.modules.about.contributor
import android.view.View
import coil.api.load
import coil.transform.RoundedCornersTransformation
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.pojos.AppCreator
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_contributor.*
class ContributorItem(val creator: AppCreator) :
AbstractFlexibleItem<ContributorItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_contributor
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) = ViewHolder(view, adapter)
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
with(holder) {
creatorItemName.text = creator.displayName
creatorItemAvatar.load("https://github.com/${creator.githubUsername}.png") {
transformations(RoundedCornersTransformation(8f))
crossfade(true)
}
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ContributorItem
if (creator != other.creator) return false
return true
}
override fun hashCode() = creator.hashCode()
class ViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) : FlexibleViewHolder(view, adapter),
LayoutContainer {
override val containerView: View? get() = contentView
}
}

View File

@ -1,6 +1,6 @@
package io.github.wulkanowy.ui.modules.about.contributor package io.github.wulkanowy.ui.modules.about.contributor
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.data.pojos.Contributor
import io.github.wulkanowy.data.repositories.appcreator.AppCreatorRepository import io.github.wulkanowy.data.repositories.appcreator.AppCreatorRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
@ -21,9 +21,8 @@ class ContributorPresenter @Inject constructor(
loadData() loadData()
} }
fun onItemSelected(item: AbstractFlexibleItem<*>) { fun onItemSelected(contributor: Contributor) {
if (item !is ContributorItem) return view?.openUserGithubPage(contributor.githubUsername)
view?.openUserGithubPage(item.creator.githubUsername)
} }
fun onSeeMoreClick() { fun onSeeMoreClick() {
@ -32,7 +31,6 @@ class ContributorPresenter @Inject constructor(
private fun loadData() { private fun loadData() {
disposable.add(appCreatorRepository.getAppCreators() disposable.add(appCreatorRepository.getAppCreators()
.map { it.map { creator -> ContributorItem(creator) } }
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
.doFinally { view?.showProgress(false) } .doFinally { view?.showProgress(false) }

View File

@ -1,12 +1,13 @@
package io.github.wulkanowy.ui.modules.about.contributor package io.github.wulkanowy.ui.modules.about.contributor
import io.github.wulkanowy.data.pojos.Contributor
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
interface ContributorView : BaseView { interface ContributorView : BaseView {
fun initView() fun initView()
fun updateData(data: List<ContributorItem>) fun updateData(data: List<Contributor>)
fun openUserGithubPage(username: String) fun openUserGithubPage(username: String)

View File

@ -0,0 +1,34 @@
package io.github.wulkanowy.ui.modules.about.license
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.mikepenz.aboutlibraries.entity.Library
import io.github.wulkanowy.databinding.ItemLicenseBinding
import javax.inject.Inject
class LicenseAdapter @Inject constructor() : RecyclerView.Adapter<LicenseAdapter.ItemViewHolder>() {
var items = emptyList<Library>()
var onClickListener: (Library) -> Unit = {}
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
ItemLicenseBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val item = items[position]
with(holder.binding) {
licenseItemName.text = item.libraryName
licenseItemSummary.text = item.license?.licenseName?.takeIf { it.isNotBlank() } ?: item.libraryVersion
root.setOnClickListener { onClickListener(item) }
}
}
class ItemViewHolder(val binding: ItemLicenseBinding) : RecyclerView.ViewHolder(binding.root)
}

View File

@ -8,16 +8,13 @@ import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.text.parseAsHtml import androidx.core.text.parseAsHtml
import androidx.recyclerview.widget.LinearLayoutManager
import com.mikepenz.aboutlibraries.Libs import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.entity.Library import com.mikepenz.aboutlibraries.entity.Library
import dagger.Lazy import dagger.Lazy
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_license.* import kotlinx.android.synthetic.main.fragment_license.*
import javax.inject.Inject import javax.inject.Inject
@ -27,7 +24,7 @@ class LicenseFragment : BaseFragment(), LicenseView, MainView.TitledView {
lateinit var presenter: LicensePresenter lateinit var presenter: LicensePresenter
@Inject @Inject
lateinit var licenseAdapter: FlexibleAdapter<AbstractFlexibleItem<*>> lateinit var licenseAdapter: LicenseAdapter
@Inject @Inject
lateinit var libs: Lazy<Libs> lateinit var libs: Lazy<Libs>
@ -53,15 +50,19 @@ class LicenseFragment : BaseFragment(), LicenseView, MainView.TitledView {
} }
override fun initView() { override fun initView() {
licenseAdapter.onClickListener = presenter::onItemSelected
with(licenseRecycler) { with(licenseRecycler) {
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
adapter = licenseAdapter adapter = licenseAdapter
} }
licenseAdapter.setOnItemClickListener(presenter::onItemSelected)
} }
override fun updateData(data: List<LicenseItem>) { override fun updateData(data: List<Library>) {
licenseAdapter.updateDataSet(data) with(licenseAdapter) {
items = data
notifyDataSetChanged()
}
} }
override fun openLicense(licenseHtml: String) { override fun openLicense(licenseHtml: String) {

View File

@ -1,44 +0,0 @@
package io.github.wulkanowy.ui.modules.about.license
import android.view.View
import com.mikepenz.aboutlibraries.entity.Library
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 kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_license.*
class LicenseItem(val library: Library) : AbstractFlexibleItem<LicenseItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_license
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) = ViewHolder(view, adapter)
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
with(holder) {
licenseItemName.text = library.libraryName
licenseItemSummary.text = library.license?.licenseName?.takeIf { it.isNotBlank() } ?: library.libraryVersion
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as LicenseItem
if (library != other.library) return false
return true
}
override fun hashCode() = library.hashCode()
class ViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) : FlexibleViewHolder(view, adapter),
LayoutContainer {
override val containerView: View? get() = contentView
}
}

View File

@ -1,6 +1,6 @@
package io.github.wulkanowy.ui.modules.about.license package io.github.wulkanowy.ui.modules.about.license
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import com.mikepenz.aboutlibraries.entity.Library
import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
@ -20,14 +20,12 @@ class LicensePresenter @Inject constructor(
loadData() loadData()
} }
fun onItemSelected(item: AbstractFlexibleItem<*>) { fun onItemSelected(library: Library) {
if (item !is LicenseItem) return view?.run { library.license?.licenseDescription?.let { openLicense(it) } }
view?.run { item.library.license?.licenseDescription?.let { openLicense(it) } }
} }
private fun loadData() { private fun loadData() {
disposable.add(Single.fromCallable { view?.appLibraries } disposable.add(Single.fromCallable { view?.appLibraries.orEmpty() }
.map { it.map { library -> LicenseItem(library) } }
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
.doOnEvent { _, _ -> view?.showProgress(false) } .doOnEvent { _, _ -> view?.showProgress(false) }

View File

@ -9,7 +9,7 @@ interface LicenseView : BaseView {
fun initView() fun initView()
fun updateData(data: List<LicenseItem>) fun updateData(data: List<Library>)
fun openLicense(licenseHtml: String) fun openLicense(licenseHtml: String)

View File

@ -0,0 +1,46 @@
package io.github.wulkanowy.ui.modules.account
import android.annotation.SuppressLint
import android.graphics.PorterDuff
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.databinding.ItemAccountBinding
import io.github.wulkanowy.utils.getThemeAttrColor
import javax.inject.Inject
class AccountAdapter @Inject constructor() : RecyclerView.Adapter<AccountAdapter.ItemViewHolder>() {
var items = emptyList<Student>()
var onClickListener: (Student) -> Unit = {}
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
ItemAccountBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val student = items[position]
with(holder.binding) {
accountItemName.text = "${student.studentName} ${student.className}"
accountItemSchool.text = student.schoolName
with(accountItemImage) {
val colorImage = if (student.isCurrent) context.getThemeAttrColor(R.attr.colorPrimary)
else context.getThemeAttrColor(R.attr.colorOnSurface, 153)
setColorFilter(colorImage, PorterDuff.Mode.SRC_IN)
}
root.setOnClickListener { onClickListener(student) }
}
}
class ItemViewHolder(val binding: ItemAccountBinding) : RecyclerView.ViewHolder(binding.root)
}

View File

@ -7,13 +7,11 @@ import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import android.widget.Toast.LENGTH_LONG import android.widget.Toast.LENGTH_LONG
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import eu.davidea.flexibleadapter.FlexibleAdapter import androidx.recyclerview.widget.LinearLayoutManager
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BaseDialogFragment import io.github.wulkanowy.ui.base.BaseDialogFragment
import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.dialog_account.* import kotlinx.android.synthetic.main.dialog_account.*
import javax.inject.Inject import javax.inject.Inject
@ -23,7 +21,7 @@ class AccountDialog : BaseDialogFragment(), AccountView {
lateinit var presenter: AccountPresenter lateinit var presenter: AccountPresenter
@Inject @Inject
lateinit var accountAdapter: FlexibleAdapter<AbstractFlexibleItem<*>> lateinit var accountAdapter: AccountAdapter
companion object { companion object {
fun newInstance() = AccountDialog() fun newInstance() = AccountDialog()
@ -44,18 +42,21 @@ class AccountDialog : BaseDialogFragment(), AccountView {
} }
override fun initView() { override fun initView() {
accountAdapter.setOnItemClickListener { presenter.onItemSelected(it) } accountAdapter.onClickListener = presenter::onItemSelected
accountDialogAdd.setOnClickListener { presenter.onAddSelected() } accountDialogAdd.setOnClickListener { presenter.onAddSelected() }
accountDialogRemove.setOnClickListener { presenter.onRemoveSelected() } accountDialogRemove.setOnClickListener { presenter.onRemoveSelected() }
accountDialogRecycler.apply { accountDialogRecycler.apply {
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
adapter = accountAdapter adapter = accountAdapter
} }
} }
override fun updateData(data: List<AccountItem>) { override fun updateData(data: List<Student>) {
accountAdapter.updateDataSet(data) with(accountAdapter) {
items = data
notifyDataSetChanged()
}
} }
override fun showError(text: String, error: Throwable) { override fun showError(text: String, error: Throwable) {

View File

@ -1,59 +0,0 @@
package io.github.wulkanowy.ui.modules.account
import android.annotation.SuppressLint
import android.graphics.PorterDuff
import android.view.View
import androidx.core.graphics.ColorUtils
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.Student
import io.github.wulkanowy.utils.getThemeAttrColor
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_account.*
class AccountItem(val student: Student) : AbstractFlexibleItem<AccountItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_account
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) = ViewHolder(view, adapter)
@SuppressLint("SetTextI18n")
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
val context = holder.contentView.context
val colorImage = if (student.isCurrent) context.getThemeAttrColor(R.attr.colorPrimary)
else ColorUtils.setAlphaComponent(context.getThemeAttrColor(R.attr.colorOnSurface), 153)
with(holder) {
accountItemName.text = "${student.studentName} ${student.className}"
accountItemSchool.text = student.schoolName
accountItemImage.setColorFilter(colorImage, PorterDuff.Mode.SRC_IN)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as AccountItem
if (student != other.student) return false
return true
}
override fun hashCode(): Int {
var result = student.hashCode()
result = 31 * result + student.id.toInt()
return result
}
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter),
LayoutContainer {
override val containerView: View? get() = contentView
}
}

View File

@ -1,6 +1,6 @@
package io.github.wulkanowy.ui.modules.account package io.github.wulkanowy.ui.modules.account
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.services.sync.SyncManager import io.github.wulkanowy.services.sync.SyncManager
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
@ -63,14 +63,13 @@ class AccountPresenter @Inject constructor(
})) }))
} }
fun onItemSelected(item: AbstractFlexibleItem<*>) { fun onItemSelected(student: Student) {
if (item is AccountItem) { Timber.i("Select student item ${student.id}")
Timber.i("Select student item ${item.student.id}") if (student.isCurrent) {
if (item.student.isCurrent) {
view?.dismissView() view?.dismissView()
} else { } else {
Timber.i("Attempt to change a student") Timber.i("Attempt to change a student")
disposable.add(studentRepository.switchStudent(item.student) disposable.add(studentRepository.switchStudent(student)
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
.doFinally { view?.dismissView() } .doFinally { view?.dismissView() }
@ -83,12 +82,10 @@ class AccountPresenter @Inject constructor(
})) }))
} }
} }
}
private fun loadData() { private fun loadData() {
Timber.i("Loading account data started") Timber.i("Loading account data started")
disposable.add(studentRepository.getSavedStudents(false) disposable.add(studentRepository.getSavedStudents(false)
.map { it.map { item -> AccountItem(item) } }
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
.subscribe({ .subscribe({
@ -100,4 +97,3 @@ class AccountPresenter @Inject constructor(
})) }))
} }
} }

View File

@ -1,12 +1,13 @@
package io.github.wulkanowy.ui.modules.account package io.github.wulkanowy.ui.modules.account
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
interface AccountView : BaseView { interface AccountView : BaseView {
fun initView() fun initView()
fun updateData(data: List<AccountItem>) fun updateData(data: List<Student>)
fun dismissView() fun dismissView()

View File

@ -1,12 +1,80 @@
package io.github.wulkanowy.ui.modules.attendance package io.github.wulkanowy.ui.modules.attendance
import eu.davidea.flexibleadapter.FlexibleAdapter import android.view.LayoutInflater
import eu.davidea.flexibleadapter.items.IFlexible import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.repositories.attendance.SentExcuseStatus
import io.github.wulkanowy.databinding.ItemAttendanceBinding
import javax.inject.Inject
class AttendanceAdapter<T : IFlexible<*>> : FlexibleAdapter<T>(null, null, true) { class AttendanceAdapter @Inject constructor() :
RecyclerView.Adapter<AttendanceAdapter.ItemViewHolder>() {
var items = emptyList<Attendance>()
var excuseActionMode: Boolean = false var excuseActionMode: Boolean = false
var onClickListener: (Attendance) -> Unit = {}
var onExcuseCheckboxSelect: (attendanceItem: Attendance, checked: Boolean) -> Unit = { _, _ -> } var onExcuseCheckboxSelect: (attendanceItem: Attendance, checked: Boolean) -> Unit = { _, _ -> }
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
ItemAttendanceBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val item = items[position]
with(holder.binding) {
attendanceItemNumber.text = item.number.toString()
attendanceItemSubject.text = item.subject
attendanceItemDescription.text = item.name
attendanceItemAlert.visibility = item.run { if (absence && !excused) View.VISIBLE else View.INVISIBLE }
attendanceItemNumber.visibility = View.GONE
attendanceItemExcuseInfo.visibility = View.GONE
attendanceItemExcuseCheckbox.visibility = View.GONE
attendanceItemExcuseCheckbox.isChecked = false
attendanceItemExcuseCheckbox.setOnCheckedChangeListener { _, checked ->
onExcuseCheckboxSelect(item, checked)
}
when (if (item.excuseStatus != null) SentExcuseStatus.valueOf(item.excuseStatus) else null) {
SentExcuseStatus.WAITING -> {
attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_waiting)
attendanceItemExcuseInfo.visibility = View.VISIBLE
attendanceItemAlert.visibility = View.INVISIBLE
}
SentExcuseStatus.DENIED -> {
attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_denied)
attendanceItemExcuseInfo.visibility = View.VISIBLE
}
else -> {
if (item.excusable && excuseActionMode) {
attendanceItemNumber.visibility = View.GONE
attendanceItemExcuseCheckbox.visibility = View.VISIBLE
} else {
attendanceItemNumber.visibility = View.VISIBLE
attendanceItemExcuseCheckbox.visibility = View.GONE
}
}
}
root.setOnClickListener {
onClickListener(item)
with(attendanceItemExcuseCheckbox) {
if (excuseActionMode && isVisible) {
isChecked = !isChecked
}
}
}
}
}
class ItemViewHolder(val binding: ItemAttendanceBinding) : RecyclerView.ViewHolder(binding.root)
} }

View File

@ -13,19 +13,17 @@ import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.recyclerview.widget.LinearLayoutManager
import com.wdullaer.materialdatetimepicker.date.DatePickerDialog import com.wdullaer.materialdatetimepicker.date.DatePickerDialog
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.R
import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.SchooldaysRangeLimiter import io.github.wulkanowy.utils.SchooldaysRangeLimiter
import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.dialog_excuse.* import kotlinx.android.synthetic.main.dialog_excuse.*
import kotlinx.android.synthetic.main.fragment_attendance.* import kotlinx.android.synthetic.main.fragment_attendance.*
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
@ -38,7 +36,7 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
lateinit var presenter: AttendancePresenter lateinit var presenter: AttendancePresenter
@Inject @Inject
lateinit var attendanceAdapter: AttendanceAdapter<AbstractFlexibleItem<*>> lateinit var attendanceAdapter: AttendanceAdapter
override val excuseSuccessString: String override val excuseSuccessString: String
get() = getString(R.string.attendance_excuse_success) get() = getString(R.string.attendance_excuse_success)
@ -54,7 +52,7 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
override val titleStringId get() = R.string.attendance_title override val titleStringId get() = R.string.attendance_title
override val isViewEmpty get() = attendanceAdapter.isEmpty override val isViewEmpty get() = attendanceAdapter.items.isEmpty()
override val currentStackSize get() = (activity as? MainActivity)?.currentStackSize override val currentStackSize get() = (activity as? MainActivity)?.currentStackSize
@ -102,15 +100,15 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
} }
override fun initView() { override fun initView() {
attendanceAdapter.setOnItemClickListener(presenter::onAttendanceItemSelected) with(attendanceAdapter) {
attendanceAdapter.onExcuseCheckboxSelect = presenter::onExcuseCheckboxSelect onClickListener = presenter::onAttendanceItemSelected
onExcuseCheckboxSelect = presenter::onExcuseCheckboxSelect
}
with(attendanceRecycler) { with(attendanceRecycler) {
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
adapter = attendanceAdapter adapter = attendanceAdapter
addItemDecoration(FlexibleItemDecoration(context) addItemDecoration(DividerItemDecoration(context))
.withDefaultDivider()
.withDrawDividerOnLastItem(false))
} }
attendanceSwipe.setOnRefreshListener(presenter::onSwipeRefresh) attendanceSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
@ -135,8 +133,11 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
else false else false
} }
override fun updateData(data: List<AttendanceItem>) { override fun updateData(data: List<Attendance>) {
attendanceAdapter.updateDataSet(data, true) with(attendanceAdapter) {
items = data
notifyDataSetChanged()
}
} }
override fun updateNavigationDay(date: String) { override fun updateNavigationDay(date: String) {
@ -144,7 +145,10 @@ class AttendanceFragment : BaseFragment(), AttendanceView, MainView.MainChildVie
} }
override fun clearData() { override fun clearData() {
attendanceAdapter.clear() with(attendanceAdapter) {
items = emptyList()
notifyDataSetChanged()
}
} }
override fun resetView() { override fun resetView() {

View File

@ -1,97 +0,0 @@
package io.github.wulkanowy.ui.modules.attendance
import android.view.View
import android.view.View.GONE
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import androidx.core.view.isVisible
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 io.github.wulkanowy.data.repositories.attendance.SentExcuseStatus
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_attendance.*
class AttendanceItem(val attendance: Attendance) :
AbstractFlexibleItem<AttendanceItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_attendance
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
return ViewHolder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
holder.apply {
attendanceItemNumber.text = attendance.number.toString()
attendanceItemSubject.text = attendance.subject
attendanceItemDescription.text = attendance.name
attendanceItemAlert.visibility = attendance.run { if (absence && !excused) VISIBLE else INVISIBLE }
attendanceItemNumber.visibility = GONE
attendanceItemExcuseInfo.visibility = GONE
attendanceItemExcuseCheckbox.visibility = GONE
attendanceItemExcuseCheckbox.isChecked = false
attendanceItemExcuseCheckbox.setOnCheckedChangeListener { _, checked ->
(adapter as AttendanceAdapter).onExcuseCheckboxSelect(attendance, checked)
}
when (if (attendance.excuseStatus != null) SentExcuseStatus.valueOf(attendance.excuseStatus) else null) {
SentExcuseStatus.WAITING -> {
attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_waiting)
attendanceItemExcuseInfo.visibility = VISIBLE
attendanceItemAlert.visibility = INVISIBLE
}
SentExcuseStatus.DENIED -> {
attendanceItemExcuseInfo.setImageResource(R.drawable.ic_excuse_denied)
attendanceItemExcuseInfo.visibility = VISIBLE
}
else -> {
if (attendance.excusable && (adapter as AttendanceAdapter).excuseActionMode) {
attendanceItemNumber.visibility = GONE
attendanceItemExcuseCheckbox.visibility = VISIBLE
} else {
attendanceItemNumber.visibility = VISIBLE
attendanceItemExcuseCheckbox.visibility = GONE
}
}
}
}
}
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 {
var result = attendance.hashCode()
result = 31 * result + attendance.id.toInt()
return result
}
class ViewHolder(view: View, val adapter: FlexibleAdapter<*>) :
FlexibleViewHolder(view, adapter),
LayoutContainer {
override val containerView: View
get() = contentView
override fun onClick(view: View?) {
super.onClick(view)
attendanceItemExcuseCheckbox.apply {
if ((adapter as AttendanceAdapter).excuseActionMode && isVisible) {
isChecked = !isChecked
}
}
}
}
}

View File

@ -1,12 +0,0 @@
package io.github.wulkanowy.ui.modules.attendance
import dagger.Module
import dagger.Provides
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
@Module
class AttendanceModule {
@Provides
fun provideAttendanceFlexibleAdapter() = AttendanceAdapter<AbstractFlexibleItem<*>>()
}

View File

@ -1,7 +1,6 @@
package io.github.wulkanowy.ui.modules.attendance package io.github.wulkanowy.ui.modules.attendance
import android.annotation.SuppressLint import android.annotation.SuppressLint
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.db.entities.Attendance import io.github.wulkanowy.data.db.entities.Attendance
import io.github.wulkanowy.data.repositories.attendance.AttendanceRepository import io.github.wulkanowy.data.repositories.attendance.AttendanceRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
@ -111,11 +110,11 @@ class AttendancePresenter @Inject constructor(
view?.finishActionMode() view?.finishActionMode()
} }
fun onAttendanceItemSelected(item: AbstractFlexibleItem<*>?) { fun onAttendanceItemSelected(attendance: Attendance) {
view?.apply { view?.apply {
if (item is AttendanceItem && !excuseActionMode) { if (!excuseActionMode) {
Timber.i("Select attendance item ${item.attendance.id}") Timber.i("Select attendance item ${attendance.id}")
showAttendanceDialog(item.attendance) showAttendanceDialog(attendance)
} }
} }
} }
@ -197,9 +196,7 @@ class AttendancePresenter @Inject constructor(
if (prefRepository.isShowPresent) list if (prefRepository.isShowPresent) list
else list.filter { !it.presence } else list.filter { !it.presence }
} }
.delay(200, MILLISECONDS) .map { items -> items.sortedBy { it.number } }
.map { items -> items.map { AttendanceItem(it) } }
.map { items -> items.sortedBy { it.attendance.number } }
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
.doFinally { .doFinally {
@ -216,7 +213,7 @@ class AttendancePresenter @Inject constructor(
showEmpty(it.isEmpty()) showEmpty(it.isEmpty())
showErrorView(false) showErrorView(false)
showContent(it.isNotEmpty()) showContent(it.isNotEmpty())
showExcuseButton(it.any { item -> item.attendance.excusable }) showExcuseButton(it.any { item -> item.excusable })
} }
analytics.logEvent("load_attendance", "items" to it.size, "force_refresh" to forceRefresh) analytics.logEvent("load_attendance", "items" to it.size, "force_refresh" to forceRefresh)
}) { }) {
@ -236,7 +233,6 @@ class AttendancePresenter @Inject constructor(
attendanceRepository.excuseForAbsence(student, semester, toExcuseList, reason) attendanceRepository.excuseForAbsence(student, semester, toExcuseList, reason)
} }
} }
.delay(200, MILLISECONDS)
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
.doOnSubscribe { .doOnSubscribe {

View File

@ -18,7 +18,7 @@ interface AttendanceView : BaseView {
fun initView() fun initView()
fun updateData(data: List<AttendanceItem>) fun updateData(data: List<Attendance>)
fun updateNavigationDay(date: String) fun updateNavigationDay(date: String)

View File

@ -0,0 +1,101 @@
package io.github.wulkanowy.ui.modules.attendance.summary
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.AttendanceSummary
import io.github.wulkanowy.databinding.ItemAttendanceSummaryBinding
import io.github.wulkanowy.databinding.ScrollableHeaderAttendanceSummaryBinding
import io.github.wulkanowy.utils.calculatePercentage
import io.github.wulkanowy.utils.getFormattedName
import org.threeten.bp.Month
import java.util.Locale
import javax.inject.Inject
class AttendanceSummaryAdapter @Inject constructor() :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private enum class ViewType(val id: Int) {
HEADER(1),
ITEM(2)
}
var items = emptyList<AttendanceSummary>()
override fun getItemCount() = items.size + 2
override fun getItemViewType(position: Int) = when (position) {
0 -> ViewType.HEADER.id
else -> ViewType.ITEM.id
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
ViewType.HEADER.id -> HeaderViewHolder(ScrollableHeaderAttendanceSummaryBinding.inflate(inflater, parent, false))
ViewType.ITEM.id -> ItemViewHolder(ItemAttendanceSummaryBinding.inflate(inflater, parent, false))
else -> throw IllegalStateException()
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is HeaderViewHolder -> bindHeaderViewHolder(holder.binding)
is ItemViewHolder -> bindItemViewHolder(holder.binding, position - 2)
}
}
private fun bindHeaderViewHolder(binding: ScrollableHeaderAttendanceSummaryBinding) {
binding.attendanceSummaryScrollableHeaderPercentage.text = formatPercentage(items.calculatePercentage())
}
private fun bindItemViewHolder(binding: ItemAttendanceSummaryBinding, position: Int) {
val item = if (position == -1) getTotalItem() else items[position]
with(binding) {
attendanceSummaryMonth.text = when (position) {
-1 -> root.context.getString(R.string.attendance_summary_total)
else -> item.month.getFormattedName()
}
attendanceSummaryPercentage.text = when (position) {
-1 -> formatPercentage(items.calculatePercentage())
else -> formatPercentage(item.calculatePercentage())
}
attendanceSummaryPresent.text = item.presence.toString()
attendanceSummaryAbsenceUnexcused.text = item.absence.toString()
attendanceSummaryAbsenceExcused.text = item.absenceExcused.toString()
attendanceSummaryAbsenceSchool.text = item.absenceForSchoolReasons.toString()
attendanceSummaryExemption.text = item.exemption.toString()
attendanceSummaryLatenessUnexcused.text = item.lateness.toString()
attendanceSummaryLatenessExcused.text = item.latenessExcused.toString()
}
}
private fun getTotalItem() = AttendanceSummary(
month = Month.APRIL,
presence = items.sumBy { it.presence },
absence = items.sumBy { it.absence },
absenceExcused = items.sumBy { it.absenceExcused },
absenceForSchoolReasons = items.sumBy { it.absenceForSchoolReasons },
exemption = items.sumBy { it.exemption },
lateness = items.sumBy { it.lateness },
latenessExcused = items.sumBy { it.latenessExcused },
diaryId = -1,
studentId = -1,
subjectId = -1
)
private fun formatPercentage(percentage: Double): String {
return if (percentage == 0.0) "0%"
else "${String.format(Locale.FRANCE, "%.2f", percentage)}%"
}
class HeaderViewHolder(val binding: ScrollableHeaderAttendanceSummaryBinding) :
RecyclerView.ViewHolder(binding.root)
class ItemViewHolder(val binding: ItemAttendanceSummaryBinding) :
RecyclerView.ViewHolder(binding.root)
}

View File

@ -9,10 +9,9 @@ import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.TextView import android.widget.TextView
import eu.davidea.flexibleadapter.FlexibleAdapter import androidx.recyclerview.widget.LinearLayoutManager
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.AttendanceSummary
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.dpToPx
@ -26,7 +25,7 @@ class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainVie
lateinit var presenter: AttendanceSummaryPresenter lateinit var presenter: AttendanceSummaryPresenter
@Inject @Inject
lateinit var attendanceSummaryAdapter: FlexibleAdapter<AbstractFlexibleItem<*>> lateinit var attendanceSummaryAdapter: AttendanceSummaryAdapter
private lateinit var subjectsAdapter: ArrayAdapter<String> private lateinit var subjectsAdapter: ArrayAdapter<String>
@ -36,11 +35,9 @@ class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainVie
fun newInstance() = AttendanceSummaryFragment() fun newInstance() = AttendanceSummaryFragment()
} }
override val totalString get() = getString(R.string.attendance_summary_total)
override val titleStringId get() = R.string.attendance_title override val titleStringId get() = R.string.attendance_title
override val isViewEmpty get() = attendanceSummaryAdapter.isEmpty override val isViewEmpty get() = attendanceSummaryAdapter.items.isEmpty()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_attendance_summary, container, false) return inflater.inflate(R.layout.fragment_attendance_summary, container, false)
@ -54,7 +51,7 @@ class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainVie
override fun initView() { override fun initView() {
with(attendanceSummaryRecycler) { with(attendanceSummaryRecycler) {
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
adapter = attendanceSummaryAdapter adapter = attendanceSummaryAdapter
} }
@ -81,16 +78,18 @@ class AttendanceSummaryFragment : BaseFragment(), AttendanceSummaryView, MainVie
} }
} }
override fun updateDataSet(data: List<AttendanceSummaryItem>, header: AttendanceSummaryScrollableHeader) { override fun updateDataSet(data: List<AttendanceSummary>) {
with(attendanceSummaryAdapter) { with(attendanceSummaryAdapter) {
updateDataSet(data, true) items = data
removeAllScrollableHeaders() notifyDataSetChanged()
addScrollableHeader(header)
} }
} }
override fun clearView() { override fun clearView() {
attendanceSummaryAdapter.clear() with(attendanceSummaryAdapter) {
items = emptyList()
notifyDataSetChanged()
}
} }
override fun showEmpty(show: Boolean) { override fun showEmpty(show: Boolean) {

View File

@ -1,80 +0,0 @@
package io.github.wulkanowy.ui.modules.attendance.summary
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 kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_attendance_summary.*
class AttendanceSummaryItem(
private val month: String,
private val percentage: String,
private val present: String,
private val absence: String,
private val excusedAbsence: String,
private val schoolAbsence: String,
private val exemption: String,
private val lateness: String,
private val excusedLateness: String
) : AbstractFlexibleItem<AttendanceSummaryItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_attendance_summary
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
return ViewHolder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
holder.apply {
attendanceSummaryMonth.text = month
attendanceSummaryPercentage.text = percentage
attendanceSummaryPresent.text = present
attendanceSummaryAbsenceUnexcused.text = absence
attendanceSummaryAbsenceExcused.text = excusedAbsence
attendanceSummaryAbsenceSchool.text = schoolAbsence
attendanceSummaryExemption.text = exemption
attendanceSummaryLatenessUnexcused.text = lateness
attendanceSummaryLatenessExcused.text = excusedLateness
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as AttendanceSummaryItem
if (month != other.month) return false
if (percentage != other.percentage) return false
if (present != other.present) return false
if (absence != other.absence) return false
if (excusedAbsence != other.excusedAbsence) return false
if (schoolAbsence != other.schoolAbsence) return false
if (exemption != other.exemption) return false
if (lateness != other.lateness) return false
if (excusedLateness != other.excusedLateness) return false
return true
}
override fun hashCode(): Int {
var result = month.hashCode()
result = 31 * result + percentage.hashCode()
result = 31 * result + present.hashCode()
result = 31 * result + absence.hashCode()
result = 31 * result + excusedAbsence.hashCode()
result = 31 * result + schoolAbsence.hashCode()
result = 31 * result + exemption.hashCode()
result = 31 * result + lateness.hashCode()
result = 31 * result + excusedLateness.hashCode()
return result
}
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = contentView
}
}

View File

@ -1,6 +1,5 @@
package io.github.wulkanowy.ui.modules.attendance.summary package io.github.wulkanowy.ui.modules.attendance.summary
import io.github.wulkanowy.data.db.entities.AttendanceSummary
import io.github.wulkanowy.data.db.entities.Subject import io.github.wulkanowy.data.db.entities.Subject
import io.github.wulkanowy.data.repositories.attendancesummary.AttendanceSummaryRepository import io.github.wulkanowy.data.repositories.attendancesummary.AttendanceSummaryRepository
import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository
@ -10,13 +9,8 @@ import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.calculatePercentage
import io.github.wulkanowy.utils.getFormattedName
import org.threeten.bp.Month import org.threeten.bp.Month
import timber.log.Timber import timber.log.Timber
import java.lang.String.format
import java.util.Locale.FRANCE
import java.util.concurrent.TimeUnit.MILLISECONDS
import javax.inject.Inject import javax.inject.Inject
class AttendanceSummaryPresenter @Inject constructor( class AttendanceSummaryPresenter @Inject constructor(
@ -88,8 +82,7 @@ class AttendanceSummaryPresenter @Inject constructor(
attendanceSummaryRepository.getAttendanceSummary(student, it, subjectId, forceRefresh) attendanceSummaryRepository.getAttendanceSummary(student, it, subjectId, forceRefresh)
} }
} }
.map { createAttendanceSummaryItems(it) to AttendanceSummaryScrollableHeader(formatPercentage(it.calculatePercentage())) } .map { items -> items.sortedByDescending { if (it.month.value <= Month.JUNE.value) it.month.value + 12 else it.month.value } }
.delay(200, MILLISECONDS)
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
.doFinally { .doFinally {
@ -102,11 +95,11 @@ class AttendanceSummaryPresenter @Inject constructor(
.subscribe({ .subscribe({
Timber.i("Loading attendance summary result: Success") Timber.i("Loading attendance summary result: Success")
view?.apply { view?.apply {
showEmpty(it.first.isEmpty()) showEmpty(it.isEmpty())
showContent(it.first.isNotEmpty()) showContent(it.isNotEmpty())
updateDataSet(it.first, it.second) updateDataSet(it)
} }
analytics.logEvent("load_attendance_summary", "items" to it.first.size, "force_refresh" to forceRefresh, "item_id" to subjectId) analytics.logEvent("load_attendance_summary", "items" to it.size, "force_refresh" to forceRefresh, "item_id" to subjectId)
}) { }) {
Timber.i("Loading attendance summary result: An exception occurred") Timber.i("Loading attendance summary result: An exception occurred")
errorHandler.dispatch(it) errorHandler.dispatch(it)
@ -150,42 +143,4 @@ class AttendanceSummaryPresenter @Inject constructor(
}) })
) )
} }
private fun createAttendanceSummaryTotalItem(attendanceSummary: List<AttendanceSummary>): AttendanceSummaryItem {
return AttendanceSummaryItem(
month = view?.totalString.orEmpty(),
percentage = formatPercentage(attendanceSummary.calculatePercentage()),
present = attendanceSummary.sumBy { it.presence }.toString(),
absence = attendanceSummary.sumBy { it.absence }.toString(),
excusedAbsence = attendanceSummary.sumBy { it.absenceExcused }.toString(),
schoolAbsence = attendanceSummary.sumBy { it.absenceForSchoolReasons }.toString(),
exemption = attendanceSummary.sumBy { it.exemption }.toString(),
lateness = attendanceSummary.sumBy { it.lateness }.toString(),
excusedLateness = attendanceSummary.sumBy { it.latenessExcused }.toString()
)
}
private fun createAttendanceSummaryItems(attendanceSummary: List<AttendanceSummary>): List<AttendanceSummaryItem> {
if (attendanceSummary.isEmpty()) return emptyList()
return listOf(createAttendanceSummaryTotalItem(attendanceSummary)) + attendanceSummary.sortedByDescending {
if (it.month.value <= Month.JUNE.value) it.month.value + 12 else it.month.value
}.map {
AttendanceSummaryItem(
month = it.month.getFormattedName(),
percentage = formatPercentage(it.calculatePercentage()),
present = it.presence.toString(),
absence = it.absence.toString(),
excusedAbsence = it.absenceExcused.toString(),
schoolAbsence = it.absenceForSchoolReasons.toString(),
exemption = it.exemption.toString(),
lateness = it.lateness.toString(),
excusedLateness = it.latenessExcused.toString()
)
}
}
private fun formatPercentage(percentage: Double): String {
return if (percentage == 0.0) "0%"
else "${format(FRANCE, "%.2f", percentage)}%"
}
} }

View File

@ -1,46 +0,0 @@
package io.github.wulkanowy.ui.modules.attendance.summary
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 kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.scrollable_header_attendance_summary.*
class AttendanceSummaryScrollableHeader(private val percentage: String) :
AbstractFlexibleItem<AttendanceSummaryScrollableHeader.ViewHolder>() {
override fun getLayoutRes() = R.layout.scrollable_header_attendance_summary
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?): ViewHolder {
return ViewHolder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder?, position: Int, payloads: MutableList<Any>?) {
holder?.apply { attendanceSummaryScrollableHeaderPercentage.text = percentage }
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as AttendanceSummaryScrollableHeader
if (percentage != other.percentage) return false
return true
}
override fun hashCode(): Int {
return percentage.hashCode()
}
class ViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?) : FlexibleViewHolder(view, adapter),
LayoutContainer {
override val containerView: View?
get() = contentView
}
}

View File

@ -1,11 +1,10 @@
package io.github.wulkanowy.ui.modules.attendance.summary package io.github.wulkanowy.ui.modules.attendance.summary
import io.github.wulkanowy.data.db.entities.AttendanceSummary
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
interface AttendanceSummaryView : BaseView { interface AttendanceSummaryView : BaseView {
val totalString: String
val isViewEmpty: Boolean val isViewEmpty: Boolean
fun initView() fun initView()
@ -24,7 +23,7 @@ interface AttendanceSummaryView : BaseView {
fun setErrorDetails(message: String) fun setErrorDetails(message: String)
fun updateDataSet(data: List<AttendanceSummaryItem>, header: AttendanceSummaryScrollableHeader) fun updateDataSet(data: List<AttendanceSummary>)
fun updateSubjects(data: ArrayList<String>) fun updateSubjects(data: ArrayList<String>)

View File

@ -0,0 +1,65 @@
package io.github.wulkanowy.ui.modules.exam
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.databinding.HeaderExamBinding
import io.github.wulkanowy.databinding.ItemExamBinding
import io.github.wulkanowy.utils.toFormattedString
import io.github.wulkanowy.utils.weekDayName
import org.threeten.bp.LocalDate
import javax.inject.Inject
class ExamAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var items = emptyList<ExamItem<*>>()
var onClickListener: (Exam) -> Unit = {}
override fun getItemCount() = items.size
override fun getItemViewType(position: Int) = items[position].viewType.id
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
ExamItem.ViewType.HEADER.id -> HeaderViewHolder(HeaderExamBinding.inflate(inflater, parent, false))
ExamItem.ViewType.ITEM.id -> ItemViewHolder(ItemExamBinding.inflate(inflater, parent, false))
else -> throw IllegalStateException()
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is HeaderViewHolder -> bindHeaderViewHolder(holder.binding, items[position].value as LocalDate)
is ItemViewHolder -> bindItemViewHolder(holder.binding, items[position].value as Exam)
}
}
@SuppressLint("DefaultLocale")
private fun bindHeaderViewHolder(binding: HeaderExamBinding, date: LocalDate) {
with(binding) {
examHeaderDay.text = date.weekDayName.capitalize()
examHeaderDate.text = date.toFormattedString()
}
}
private fun bindItemViewHolder(binding: ItemExamBinding, exam: Exam) {
with(binding) {
examItemSubject.text = exam.subject
examItemTeacher.text = exam.teacher
examItemType.text = exam.type
root.setOnClickListener { onClickListener(exam) }
}
}
private class HeaderViewHolder(val binding: HeaderExamBinding) :
RecyclerView.ViewHolder(binding.root)
private class ItemViewHolder(val binding: ItemExamBinding) :
RecyclerView.ViewHolder(binding.root)
}

View File

@ -7,17 +7,14 @@ import android.view.View.GONE
import android.view.View.INVISIBLE import android.view.View.INVISIBLE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter import androidx.recyclerview.widget.LinearLayoutManager
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.R
import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_exam.* import kotlinx.android.synthetic.main.fragment_exam.*
import javax.inject.Inject import javax.inject.Inject
@ -27,7 +24,7 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView.
lateinit var presenter: ExamPresenter lateinit var presenter: ExamPresenter
@Inject @Inject
lateinit var examAdapter: FlexibleAdapter<AbstractFlexibleItem<*>> lateinit var examAdapter: ExamAdapter
companion object { companion object {
private const val SAVED_DATE_KEY = "CURRENT_DATE" private const val SAVED_DATE_KEY = "CURRENT_DATE"
@ -37,7 +34,7 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView.
override val titleStringId get() = R.string.exam_title override val titleStringId get() = R.string.exam_title
override val isViewEmpty get() = examAdapter.isEmpty override val isViewEmpty get() = examAdapter.items.isEmpty()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_exam, container, false) return inflater.inflate(R.layout.fragment_exam, container, false)
@ -50,14 +47,12 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView.
} }
override fun initView() { override fun initView() {
examAdapter.setOnItemClickListener(presenter::onExamItemSelected) examAdapter.onClickListener = presenter::onExamItemSelected
with(examRecycler) { with(examRecycler) {
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
adapter = examAdapter adapter = examAdapter
addItemDecoration(FlexibleItemDecoration(context) addItemDecoration(DividerItemDecoration(context))
.withDefaultDivider(R.layout.item_exam)
.withDrawDividerOnLastItem(false))
} }
examSwipe.setOnRefreshListener(presenter::onSwipeRefresh) examSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
@ -74,8 +69,11 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView.
examSwipe.isRefreshing = false examSwipe.isRefreshing = false
} }
override fun updateData(data: List<ExamItem>) { override fun updateData(data: List<ExamItem<*>>) {
examAdapter.updateDataSet(data, true) with(examAdapter) {
items = data
notifyDataSetChanged()
}
} }
override fun updateNavigationWeek(date: String) { override fun updateNavigationWeek(date: String) {
@ -83,7 +81,10 @@ class ExamFragment : BaseFragment(), ExamView, MainView.MainChildView, MainView.
} }
override fun clearData() { override fun clearData() {
examAdapter.clear() with(examAdapter) {
items = emptyList()
notifyDataSetChanged()
}
} }
override fun resetView() { override fun resetView() {

View File

@ -1,52 +0,0 @@
package io.github.wulkanowy.ui.modules.exam
import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractHeaderItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.ExpandableViewHolder
import io.github.wulkanowy.R
import io.github.wulkanowy.utils.toFormattedString
import io.github.wulkanowy.utils.weekDayName
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.header_exam.*
import org.threeten.bp.LocalDate
class ExamHeader(private val date: LocalDate) : AbstractHeaderItem<ExamHeader.ViewHolder>() {
override fun getLayoutRes() = R.layout.header_exam
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?): ViewHolder {
return ViewHolder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder,
position: Int, payloads: MutableList<Any>?) {
holder.run {
examHeaderDay.text = date.weekDayName.capitalize()
examHeaderDate.text = date.toFormattedString()
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ExamHeader
if (date != other.date) return false
return true
}
override fun hashCode(): Int {
return date.hashCode()
}
class ViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?) : ExpandableViewHolder(view, adapter),
LayoutContainer {
override val containerView: View
get() = contentView
}
}

View File

@ -1,50 +1,9 @@
package io.github.wulkanowy.ui.modules.exam package io.github.wulkanowy.ui.modules.exam
import android.view.View data class ExamItem<out T>(val value: T, val viewType: ViewType) {
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractSectionableItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Exam
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_exam.*
class ExamItem(header: ExamHeader, val exam: Exam) : AbstractSectionableItem<ExamItem.ViewHolder, ExamHeader>(header) { enum class ViewType(val id: Int) {
HEADER(1),
override fun getLayoutRes() = R.layout.item_exam ITEM(2)
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
return ViewHolder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
holder.run {
examItemSubject.text = exam.subject
examItemTeacher.text = exam.teacher
examItemType.text = exam.type
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ExamItem
if (exam != other.exam) return false
return true
}
override fun hashCode(): Int {
var result = exam.hashCode()
result = 31 * result + exam.id.toInt()
return result
}
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = contentView
} }
} }

View File

@ -1,6 +1,5 @@
package io.github.wulkanowy.ui.modules.exam package io.github.wulkanowy.ui.modules.exam
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.db.entities.Exam import io.github.wulkanowy.data.db.entities.Exam
import io.github.wulkanowy.data.repositories.exam.ExamRepository import io.github.wulkanowy.data.repositories.exam.ExamRepository
import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository
@ -19,7 +18,6 @@ import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDate.now import org.threeten.bp.LocalDate.now
import org.threeten.bp.LocalDate.ofEpochDay import org.threeten.bp.LocalDate.ofEpochDay
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit.MILLISECONDS
import javax.inject.Inject import javax.inject.Inject
class ExamPresenter @Inject constructor( class ExamPresenter @Inject constructor(
@ -75,11 +73,9 @@ class ExamPresenter @Inject constructor(
view?.showErrorDetailsDialog(lastError) view?.showErrorDetailsDialog(lastError)
} }
fun onExamItemSelected(item: AbstractFlexibleItem<*>?) { fun onExamItemSelected(exam: Exam) {
if (item is ExamItem) { Timber.i("Select exam item ${exam.id}")
Timber.i("Select exam item ${item.exam.id}") view?.showExamDialog(exam)
view?.showExamDialog(item.exam)
}
} }
fun onViewReselected() { fun onViewReselected() {
@ -117,8 +113,6 @@ class ExamPresenter @Inject constructor(
examRepository.getExams(student, semester, currentDate.monday, currentDate.friday, forceRefresh) examRepository.getExams(student, semester, currentDate.monday, currentDate.friday, forceRefresh)
} }
} }
.delay(200, MILLISECONDS)
.map { it.groupBy { exam -> exam.date }.toSortedMap() }
.map { createExamItems(it) } .map { createExamItems(it) }
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
@ -156,12 +150,12 @@ class ExamPresenter @Inject constructor(
} }
} }
private fun createExamItems(items: Map<LocalDate, List<Exam>>): List<ExamItem> { private fun createExamItems(items: List<Exam>): List<ExamItem<*>> {
return items.flatMap { return items.groupBy { it.date }.toSortedMap().map { (date, exams) ->
ExamHeader(it.key).let { header -> listOf(ExamItem(date, ExamItem.ViewType.HEADER)) + exams.reversed().map { exam ->
it.value.reversed().map { item -> ExamItem(header, item) } ExamItem(exam, ExamItem.ViewType.ITEM)
}
} }
}.flatten()
} }
private fun reloadView() { private fun reloadView() {

View File

@ -9,7 +9,7 @@ interface ExamView : BaseView {
fun initView() fun initView()
fun updateData(data: List<ExamItem>) fun updateData(data: List<ExamItem<*>>)
fun updateNavigationWeek(date: String) fun updateNavigationWeek(date: String)

View File

@ -0,0 +1,176 @@
package io.github.wulkanowy.ui.modules.grade.details
import android.annotation.SuppressLint
import android.content.res.Resources
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.databinding.HeaderGradeDetailsBinding
import io.github.wulkanowy.databinding.ItemGradeDetailsBinding
import io.github.wulkanowy.ui.base.BaseExpandableAdapter
import io.github.wulkanowy.utils.getBackgroundColor
import io.github.wulkanowy.utils.toFormattedString
import javax.inject.Inject
class GradeDetailsAdapter @Inject constructor() : BaseExpandableAdapter<RecyclerView.ViewHolder>() {
private var headers = mutableListOf<GradeDetailsItem>()
private var items = mutableListOf<GradeDetailsItem>()
private var expandedPosition = RecyclerView.NO_POSITION
private var isExpandable = false
var onClickListener: (Grade, position: Int) -> Unit = { _, _ -> }
var colorTheme = ""
fun setDataItems(data: List<GradeDetailsItem>, isExpanded: Boolean = isExpandable) {
headers = data.filter { it.viewType == ViewType.HEADER }.toMutableList()
items = if (isExpanded) headers else data.toMutableList()
isExpandable = isExpanded
expandedPosition = RecyclerView.NO_POSITION
}
fun updateDetailsItem(position: Int, grade: Grade) {
items[position] = GradeDetailsItem(grade, ViewType.ITEM)
notifyItemChanged(position)
}
fun getHeaderItem(subject: String): GradeDetailsItem {
return headers.single { (it.value as GradeDetailsHeader).subject == subject }
}
fun updateHeaderItem(item: GradeDetailsItem) {
headers[headers.indexOf(item)] = item
items[items.indexOf(item)] = item
notifyItemChanged(items.indexOf(item))
}
fun collapseAll() {
if (expandedPosition != -1) {
refreshList(headers)
expandedPosition = RecyclerView.NO_POSITION
}
}
@Synchronized
private fun refreshList(newItems: List<GradeDetailsItem>) {
val diffCallback = GradeDetailsDiffUtil(items, newItems)
val diffResult = DiffUtil.calculateDiff(diffCallback)
items = newItems.toMutableList()
diffResult.dispatchUpdatesTo(this)
}
override fun getItemCount() = items.size
override fun getItemViewType(position: Int) = items[position].viewType.id
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
ViewType.HEADER.id -> HeaderViewHolder(HeaderGradeDetailsBinding.inflate(inflater, parent, false))
ViewType.ITEM.id -> ItemViewHolder(ItemGradeDetailsBinding.inflate(inflater, parent, false))
else -> throw IllegalStateException()
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is HeaderViewHolder -> bindHeaderViewHolder(
binding = holder.binding,
header = items[position].value as GradeDetailsHeader,
headerPosition = headers.indexOf(items[position]),
adapterPosition = position
)
is ItemViewHolder -> bindItemViewHolder(
binding = holder.binding,
grade = items[position].value as Grade,
position = position
)
}
}
private fun bindHeaderViewHolder(binding: HeaderGradeDetailsBinding, header: GradeDetailsHeader, headerPosition: Int, adapterPosition: Int) {
with(binding) {
gradeHeaderDivider.visibility = if (adapterPosition == 0) View.GONE else View.VISIBLE
gradeHeaderSubject.apply {
text = header.subject
maxLines = if (headerPosition == expandedPosition) 2 else 1
}
gradeHeaderAverage.text = formatAverage(header.average, root.context.resources)
gradeHeaderPointsSum.text = root.context.getString(R.string.grade_points_sum, header.pointsSum)
gradeHeaderPointsSum.visibility = if (!header.pointsSum.isNullOrEmpty()) View.VISIBLE else View.GONE
gradeHeaderNumber.text = root.context.resources.getQuantityString(R.plurals.grade_number_item, header.number, header.number)
gradeHeaderNote.visibility = if (header.newGrades > 0) View.VISIBLE else View.GONE
if (header.newGrades > 0) gradeHeaderNote.text = header.newGrades.toString(10)
gradeHeaderContainer.isEnabled = isExpandable
gradeHeaderContainer.setOnClickListener {
expandedPosition = if (expandedPosition == adapterPosition) -1 else adapterPosition
if (expandedPosition != RecyclerView.NO_POSITION) {
refreshList(headers.toMutableList().apply {
addAll(headerPosition + 1, header.grades)
})
scrollToHeaderWithSubItems(headerPosition, header.grades.size)
} else {
refreshList(headers)
}
}
}
}
private fun formatAverage(average: Double?, resources: Resources): String {
return if (average == null || average == .0) resources.getString(R.string.grade_no_average)
else resources.getString(R.string.grade_average, average)
}
@SuppressLint("SetTextI18n")
private fun bindItemViewHolder(binding: ItemGradeDetailsBinding, grade: Grade, position: Int) {
with(binding) {
gradeItemValue.run {
text = grade.entry
setBackgroundResource(grade.getBackgroundColor(colorTheme))
}
gradeItemDescription.text = when {
grade.description.isNotBlank() -> grade.description
grade.gradeSymbol.isNotBlank() -> grade.gradeSymbol
else -> root.context.getString(R.string.all_no_description)
}
gradeItemDate.text = grade.date.toFormattedString()
gradeItemWeight.text = "${root.context.getString(R.string.grade_weight)}: ${grade.weight}"
gradeItemNote.visibility = if (!grade.isRead) View.VISIBLE else View.GONE
root.setOnClickListener { onClickListener(grade, position) }
}
}
private class HeaderViewHolder(val binding: HeaderGradeDetailsBinding) :
RecyclerView.ViewHolder(binding.root)
private class ItemViewHolder(val binding: ItemGradeDetailsBinding) :
RecyclerView.ViewHolder(binding.root)
class GradeDetailsDiffUtil(private val old: List<GradeDetailsItem>, private val new: List<GradeDetailsItem>) :
DiffUtil.Callback() {
override fun getOldListSize() = old.size
override fun getNewListSize() = new.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return old[oldItemPosition] == new[newItemPosition]
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return old[oldItemPosition] == new[newItemPosition]
}
}
}

View File

@ -10,18 +10,13 @@ import android.view.View.GONE
import android.view.View.INVISIBLE import android.view.View.INVISIBLE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter import androidx.recyclerview.widget.LinearLayoutManager
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IExpandable
import eu.davidea.flexibleadapter.items.IFlexible
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment
import io.github.wulkanowy.ui.modules.grade.GradeView import io.github.wulkanowy.ui.modules.grade.GradeView
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_grade_details.* import kotlinx.android.synthetic.main.fragment_grade_details.*
import javax.inject.Inject import javax.inject.Inject
@ -31,7 +26,7 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh
lateinit var presenter: GradeDetailsPresenter lateinit var presenter: GradeDetailsPresenter
@Inject @Inject
lateinit var gradeDetailsAdapter: FlexibleAdapter<AbstractFlexibleItem<*>> lateinit var gradeDetailsAdapter: GradeDetailsAdapter
private var gradeDetailsMenu: Menu? = null private var gradeDetailsMenu: Menu? = null
@ -39,23 +34,8 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh
fun newInstance() = GradeDetailsFragment() fun newInstance() = GradeDetailsFragment()
} }
override val emptyAverageString: String
get() = getString(R.string.grade_no_average)
override val averageString: String
get() = getString(R.string.grade_average)
override val pointsSumString: String
get() = getString(R.string.grade_points_sum)
override val weightString: String
get() = getString(R.string.grade_weight)
override val noDescriptionString: String
get() = getString(R.string.all_no_description)
override val isViewEmpty override val isViewEmpty
get() = gradeDetailsAdapter.isEmpty get() = gradeDetailsAdapter.itemCount == 0
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -79,18 +59,11 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh
} }
override fun initView() { override fun initView() {
gradeDetailsAdapter.run { gradeDetailsAdapter.onClickListener = presenter::onGradeItemSelected
isAutoCollapseOnExpand = true
isAutoScrollOnExpand = true
setOnItemClickListener { presenter.onGradeItemSelected(it) }
}
gradeDetailsRecycler.run { gradeDetailsRecycler.run {
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
adapter = gradeDetailsAdapter adapter = gradeDetailsAdapter
addItemDecoration(GradeDetailsHeaderItemDecoration(context)
.withDefaultDivider(R.layout.header_grade_details)
)
} }
gradeDetailsSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } gradeDetailsSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
gradeDetailsErrorRetry.setOnClickListener { presenter.onRetry() } gradeDetailsErrorRetry.setOnClickListener { presenter.onRetry() }
@ -102,16 +75,23 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh
else false else false
} }
override fun updateData(data: List<GradeDetailsHeader>) { override fun updateData(data: List<GradeDetailsItem>, isGradeExpandable: Boolean, gradeColorTheme: String) {
gradeDetailsAdapter.updateDataSet(data, true) with(gradeDetailsAdapter) {
colorTheme = gradeColorTheme
setDataItems(data, isGradeExpandable)
notifyDataSetChanged()
}
} }
override fun updateItem(item: AbstractFlexibleItem<*>) { override fun updateItem(item: Grade, position: Int) {
gradeDetailsAdapter.updateItem(item) gradeDetailsAdapter.updateDetailsItem(position, item)
} }
override fun clearView() { override fun clearView() {
gradeDetailsAdapter.clear() with(gradeDetailsAdapter) {
setDataItems(mutableListOf())
notifyDataSetChanged()
}
} }
override fun collapseAllItems() { override fun collapseAllItems() {
@ -119,15 +99,15 @@ class GradeDetailsFragment : BaseFragment(), GradeDetailsView, GradeView.GradeCh
} }
override fun scrollToStart() { override fun scrollToStart() {
gradeDetailsRecycler.scrollToPosition(0) gradeDetailsRecycler.smoothScrollToPosition(0)
} }
override fun getHeaderOfItem(item: AbstractFlexibleItem<*>): IExpandable<*, out IFlexible<*>>? { override fun getHeaderOfItem(subject: String): GradeDetailsItem {
return gradeDetailsAdapter.getExpandableOf(item) return gradeDetailsAdapter.getHeaderItem(subject)
} }
override fun getGradeNumberString(number: Int): String { override fun updateHeaderItem(item: GradeDetailsItem) {
return resources.getQuantityString(R.plurals.grade_number_item, number, number) gradeDetailsAdapter.updateHeaderItem(item)
} }
override fun showProgress(show: Boolean) { override fun showProgress(show: Boolean) {

View File

@ -1,94 +0,0 @@
package io.github.wulkanowy.ui.modules.grade.details
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractExpandableItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.ExpandableViewHolder
import io.github.wulkanowy.R
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.header_grade_details.*
class GradeDetailsHeader(
private val subject: String,
private val number: String,
private val average: String,
private val pointsSum: String,
var newGrades: Int,
private val isExpandable: Boolean
) : AbstractExpandableItem<GradeDetailsHeader.ViewHolder, GradeDetailsItem>() {
init {
isExpanded = !isExpandable
}
override fun getLayoutRes() = R.layout.header_grade_details
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?): ViewHolder {
return ViewHolder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
holder.run {
gradeHeaderSubject.apply {
text = subject
maxLines = if (isExpanded) 2 else 1
}
gradeHeaderAverage.text = average
gradeHeaderPointsSum.text = pointsSum
gradeHeaderPointsSum.visibility = if (pointsSum.isNotEmpty()) VISIBLE else GONE
gradeHeaderNumber.text = number
gradeHeaderNote.visibility = if (newGrades > 0) VISIBLE else GONE
if (newGrades > 0) gradeHeaderNote.text = newGrades.toString(10)
gradeHeaderContainer.isEnabled = isExpandable
isViewExpandable = isExpandable
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as GradeDetailsHeader
if (subject != other.subject) return false
if (number != other.number) return false
if (average != other.average) return false
if (isExpandable != other.isExpandable) return false
return true
}
override fun hashCode(): Int {
var result = subject.hashCode()
result = 31 * result + number.hashCode()
result = 31 * result + average.hashCode()
result = 31 * result + isExpandable.hashCode()
return result
}
class ViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?) :
ExpandableViewHolder(view, adapter), LayoutContainer {
var isViewExpandable = true
init {
contentView.setOnClickListener(this)
}
override val containerView: View
get() = contentView
override fun isViewCollapsibleOnClick() = isViewExpandable
override fun isViewExpandableOnClick() = isViewExpandable
override fun onClick(view: View?) {
super.onClick(view)
mAdapter.getItem(adapterPosition)?.let { mAdapter.updateItem(it) }
}
}
}

View File

@ -1,38 +0,0 @@
package io.github.wulkanowy.ui.modules.grade.details
import android.content.Context
import android.graphics.Canvas
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.common.FlexibleItemDecoration
class GradeDetailsHeaderItemDecoration(context: Context) : FlexibleItemDecoration(context) {
override fun drawVertical(canvas: Canvas, parent: RecyclerView) {
canvas.save()
val left: Int
val right: Int
if (parent.clipToPadding) {
left = parent.paddingLeft
right = parent.width - parent.paddingRight
canvas.clipRect(left, parent.paddingTop, right,
parent.height - parent.paddingBottom)
} else {
left = 0
right = parent.width
}
val itemCount = parent.childCount
for (i in 1 until itemCount) {
val child = parent.getChildAt(i)
val viewHolder = parent.getChildViewHolder(child)
if (shouldDrawDivider(viewHolder)) {
parent.getDecoratedBoundsWithMargins(child, mBounds)
val bottom = mBounds.top + Math.round(child.translationY)
val top = bottom - mDivider.intrinsicHeight
mDivider.setBounds(left, top, right, bottom)
mDivider.draw(canvas)
}
}
canvas.restore()
}
}

View File

@ -1,74 +1,20 @@
package io.github.wulkanowy.ui.modules.grade.details package io.github.wulkanowy.ui.modules.grade.details
import android.annotation.SuppressLint enum class ViewType(val id: Int) {
import android.view.View HEADER(1),
import android.view.View.GONE ITEM(2)
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.Grade
import io.github.wulkanowy.utils.toFormattedString
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_grade_details.*
class GradeDetailsItem(
val grade: Grade,
private val valueBgColor: Int,
private val weightString: String,
private val noDescriptionString: String
) : AbstractFlexibleItem<GradeDetailsItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_grade_details
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
return ViewHolder(view, adapter)
}
@SuppressLint("SetTextI18n")
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
holder.run {
gradeItemValue.run {
text = grade.entry
setBackgroundResource(valueBgColor)
}
gradeItemDescription.text = when {
grade.description.isNotBlank() -> grade.description
grade.gradeSymbol.isNotBlank() -> grade.gradeSymbol
else -> noDescriptionString
}
gradeItemDate.text = grade.date.toFormattedString()
gradeItemWeight.text = "$weightString: ${grade.weight}"
gradeItemNote.visibility = if (!grade.isRead) VISIBLE else GONE
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as GradeDetailsItem
if (grade != other.grade) return false
if (grade.id != other.grade.id) return false
if (weightString != other.weightString) return false
if (valueBgColor != other.valueBgColor) return false
return true
}
override fun hashCode(): Int {
var result = grade.hashCode()
result = 31 * result + grade.id.toInt()
result = 31 * result + weightString.hashCode()
result = 31 * result + valueBgColor
return result
}
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = contentView
}
} }
data class GradeDetailsItem(
val value: Any,
val viewType: ViewType
)
data class GradeDetailsHeader(
val subject: String,
val number: Int,
val average: Double?,
val pointsSum: String?,
var newGrades: Int,
val grades: List<GradeDetailsItem>
)

View File

@ -1,6 +1,5 @@
package io.github.wulkanowy.ui.modules.grade.details package io.github.wulkanowy.ui.modules.grade.details
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.repositories.grade.GradeRepository import io.github.wulkanowy.data.repositories.grade.GradeRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
@ -11,7 +10,6 @@ import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.getBackgroundColor
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -43,24 +41,20 @@ class GradeDetailsPresenter @Inject constructor(
loadData(semesterId, forceRefresh) loadData(semesterId, forceRefresh)
} }
fun onGradeItemSelected(item: AbstractFlexibleItem<*>?) { fun onGradeItemSelected(grade: Grade, position: Int) {
if (item is GradeDetailsItem) { Timber.i("Select grade item ${grade.id}")
Timber.i("Select grade item ${item.grade.id}")
view?.apply { view?.apply {
showGradeDialog(item.grade, preferencesRepository.gradeColorTheme) showGradeDialog(grade, preferencesRepository.gradeColorTheme)
if (!item.grade.isRead) { if (!grade.isRead) {
item.grade.isRead = true grade.isRead = true
updateItem(item) updateItem(grade, position)
getHeaderOfItem(item)?.let { header -> getHeaderOfItem(grade.subject).let { header ->
if (header is GradeDetailsHeader) { (header.value as GradeDetailsHeader).newGrades--
header.newGrades-- updateHeaderItem(header)
updateItem(header)
}
} }
newGradesAmount-- newGradesAmount--
updateMarkAsDoneButton() updateMarkAsDoneButton()
updateGrade(item.grade) updateGrade(grade)
}
} }
} }
} }
@ -134,11 +128,9 @@ class GradeDetailsPresenter @Inject constructor(
disposable.add(studentRepository.getCurrentStudent() disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getSemesters(it).map { semester -> it to semester } } .flatMap { semesterRepository.getSemesters(it).map { semester -> it to semester } }
.flatMap { (student, semesters) -> .flatMap { (student, semesters) ->
averageProvider.getGradeAverage(student, semesters, semesterId, forceRefresh) averageProvider.getGradeAverage(student, semesters, semesterId, forceRefresh).flatMap { averages ->
.flatMap { averages ->
gradeRepository.getGrades(student, semesters.first { it.semesterId == semesterId }, forceRefresh) gradeRepository.getGrades(student, semesters.first { it.semesterId == semesterId }, forceRefresh)
.map { it.sortedByDescending { grade -> grade.date } } .map { it.sortedByDescending { grade -> grade.date } }
.map { it.groupBy { grade -> grade.subject }.toSortedMap() }
.map { createGradeItems(it, averages) } .map { createGradeItems(it, averages) }
} }
} }
@ -152,17 +144,23 @@ class GradeDetailsPresenter @Inject constructor(
notifyParentDataLoaded(semesterId) notifyParentDataLoaded(semesterId)
} }
} }
.subscribe({ .subscribe({ grades ->
Timber.i("Loading grade details result: Success") Timber.i("Loading grade details result: Success")
newGradesAmount = it.sumBy { gradeDetailsHeader -> gradeDetailsHeader.newGrades } newGradesAmount = grades
.filter { it.viewType == ViewType.HEADER }
.sumBy { item -> (item.value as GradeDetailsHeader).newGrades }
updateMarkAsDoneButton() updateMarkAsDoneButton()
view?.run { view?.run {
showEmpty(it.isEmpty()) showEmpty(grades.isEmpty())
showErrorView(false) showErrorView(false)
showContent(it.isNotEmpty()) showContent(grades.isNotEmpty())
updateData(it) updateData(
data = grades,
isGradeExpandable = preferencesRepository.isGradeExpandable,
gradeColorTheme = preferencesRepository.gradeColorTheme
)
} }
analytics.logEvent("load_grade_details", "items" to it.size, "force_refresh" to forceRefresh) analytics.logEvent("load_grade_details", "items" to grades.size, "force_refresh" to forceRefresh)
}) { }) {
Timber.i("Loading grade details result: An exception occurred") Timber.i("Loading grade details result: An exception occurred")
errorHandler.dispatch(it) errorHandler.dispatch(it)
@ -180,40 +178,21 @@ class GradeDetailsPresenter @Inject constructor(
} }
} }
private fun createGradeItems(items: Map<String, List<Grade>>, averages: List<Triple<String, Double, String>>): List<GradeDetailsHeader> { private fun createGradeItems(items: List<Grade>, averages: List<Triple<String, Double, String>>): List<GradeDetailsItem> {
val isGradeExpandable = preferencesRepository.isGradeExpandable return items.groupBy { grade -> grade.subject }.toSortedMap().map { (subject, grades) ->
val gradeColorTheme = preferencesRepository.gradeColorTheme val subItems = grades.map {
GradeDetailsItem(it, ViewType.ITEM)
val noDescriptionString = view?.noDescriptionString.orEmpty()
val weightString = view?.weightString.orEmpty()
val pointsSumString = view?.pointsSumString.orEmpty()
return items.map { subject ->
GradeDetailsHeader(
subject = subject.key,
average = formatAverage(averages.singleOrNull { subject.key == it.first }?.second),
pointsSum = averages.singleOrNull { subject.key == it.first }?.takeIf { it.third.isNotEmpty() }?.let { pointsSumString.format(it.third) }.orEmpty(),
number = view?.getGradeNumberString(subject.value.size).orEmpty(),
newGrades = subject.value.filter { grade -> !grade.isRead }.size,
isExpandable = isGradeExpandable
).apply {
subItems = subject.value.map { item ->
GradeDetailsItem(
grade = item,
valueBgColor = item.getBackgroundColor(gradeColorTheme),
weightString = weightString,
noDescriptionString = noDescriptionString
)
}
}
}
} }
private fun formatAverage(average: Double?): String { listOf(GradeDetailsItem(GradeDetailsHeader(
return view?.run { subject = subject,
if (average == null || average == .0) emptyAverageString average = averages.singleOrNull { subject == it.first }?.second,
else averageString.format(average) pointsSum = averages.singleOrNull { subject == it.first }?.third,
}.orEmpty() number = grades.size,
newGrades = grades.filter { grade -> !grade.isRead }.size,
grades = subItems
), ViewType.HEADER)) + if (preferencesRepository.isGradeExpandable) emptyList() else subItems
}.flatten()
} }
private fun updateGrade(grade: Grade) { private fun updateGrade(grade: Grade) {
@ -221,8 +200,9 @@ class GradeDetailsPresenter @Inject constructor(
disposable.add(gradeRepository.updateGrade(grade) disposable.add(gradeRepository.updateGrade(grade)
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
.subscribe({ Timber.i("Update grade result: Success") }) .subscribe({
{ error -> Timber.i("Update grade result: Success")
}) { error ->
Timber.i("Update grade result: An exception occurred") Timber.i("Update grade result: An exception occurred")
errorHandler.dispatch(error) errorHandler.dispatch(error)
}) })

View File

@ -1,8 +1,5 @@
package io.github.wulkanowy.ui.modules.grade.details package io.github.wulkanowy.ui.modules.grade.details
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IExpandable
import eu.davidea.flexibleadapter.items.IFlexible
import io.github.wulkanowy.data.db.entities.Grade import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
@ -10,21 +7,13 @@ interface GradeDetailsView : BaseView {
val isViewEmpty: Boolean val isViewEmpty: Boolean
val emptyAverageString: String
val averageString: String
val pointsSumString: String
val weightString: String
val noDescriptionString: String
fun initView() fun initView()
fun updateData(data: List<GradeDetailsHeader>) fun updateData(data: List<GradeDetailsItem>, isGradeExpandable: Boolean, gradeColorTheme: String)
fun updateItem(item: AbstractFlexibleItem<*>) fun updateItem(item: Grade, position: Int)
fun updateHeaderItem(item: GradeDetailsItem)
fun clearView() fun clearView()
@ -54,7 +43,5 @@ interface GradeDetailsView : BaseView {
fun enableMarkAsDoneButton(enable: Boolean) fun enableMarkAsDoneButton(enable: Boolean)
fun getGradeNumberString(number: Int): String fun getHeaderOfItem(subject: String): GradeDetailsItem
fun getHeaderOfItem(item: AbstractFlexibleItem<*>): IExpandable<*, out IFlexible<*>>?
} }

View File

@ -0,0 +1,85 @@
package io.github.wulkanowy.ui.modules.grade.summary
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.databinding.ItemGradeSummaryBinding
import io.github.wulkanowy.databinding.ScrollableHeaderGradeSummaryBinding
import io.github.wulkanowy.utils.calcAverage
import java.util.Locale
import javax.inject.Inject
class GradeSummaryAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private enum class ViewType(val id: Int) {
HEADER(1),
ITEM(2)
}
var items = emptyList<GradeSummary>()
override fun getItemCount() = items.size + 1
override fun getItemViewType(position: Int) = when (position) {
0 -> ViewType.HEADER.id
else -> ViewType.ITEM.id
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
ViewType.HEADER.id -> HeaderViewHolder(ScrollableHeaderGradeSummaryBinding.inflate(inflater, parent, false))
ViewType.ITEM.id -> ItemViewHolder(ItemGradeSummaryBinding.inflate(inflater, parent, false))
else -> throw IllegalStateException()
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is HeaderViewHolder -> bindHeaderViewHolder(holder.binding)
is ItemViewHolder -> bindItemViewHolder(holder.binding, items[position - 1])
}
}
private fun bindHeaderViewHolder(binding: ScrollableHeaderGradeSummaryBinding) {
if (items.isEmpty()) return
with(binding) {
gradeSummaryScrollableHeaderFinal.text = formatAverage(items.calcAverage())
gradeSummaryScrollableHeaderCalculated.text = formatAverage(items
.filter { value -> value.average != 0.0 }
.map { values -> values.average }
.reversed() // fix average precision
.average()
)
}
}
@SuppressLint("SetTextI18n")
private fun bindItemViewHolder(binding: ItemGradeSummaryBinding, item: GradeSummary) {
with(binding) {
gradeSummaryItemTitle.text = item.subject
gradeSummaryItemPoints.text = item.pointsSum
gradeSummaryItemAverage.text = formatAverage(item.average, "")
gradeSummaryItemPredicted.text = "${item.predictedGrade} ${item.proposedPoints}".trim()
gradeSummaryItemFinal.text = "${item.finalGrade} ${item.finalPoints}".trim()
gradeSummaryItemPointsContainer.visibility = if (item.pointsSum.isBlank()) View.GONE else View.VISIBLE
}
}
private fun formatAverage(average: Double, defaultValue: String = "-- --"): String {
return if (average == 0.0) defaultValue
else String.format(Locale.FRANCE, "%.2f", average)
}
private class HeaderViewHolder(val binding: ScrollableHeaderGradeSummaryBinding) :
RecyclerView.ViewHolder(binding.root)
private class ItemViewHolder(val binding: ItemGradeSummaryBinding) :
RecyclerView.ViewHolder(binding.root)
}

View File

@ -7,10 +7,9 @@ import android.view.View.GONE
import android.view.View.INVISIBLE import android.view.View.INVISIBLE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter import androidx.recyclerview.widget.LinearLayoutManager
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment
import io.github.wulkanowy.ui.modules.grade.GradeView import io.github.wulkanowy.ui.modules.grade.GradeView
@ -23,14 +22,14 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh
lateinit var presenter: GradeSummaryPresenter lateinit var presenter: GradeSummaryPresenter
@Inject @Inject
lateinit var gradeSummaryAdapter: FlexibleAdapter<AbstractFlexibleItem<*>> lateinit var gradeSummaryAdapter: GradeSummaryAdapter
companion object { companion object {
fun newInstance() = GradeSummaryFragment() fun newInstance() = GradeSummaryFragment()
} }
override val isViewEmpty override val isViewEmpty
get() = gradeSummaryAdapter.isEmpty get() = gradeSummaryAdapter.items.isEmpty()
override val predictedString override val predictedString
get() = getString(R.string.grade_summary_predicted_grade) get() = getString(R.string.grade_summary_predicted_grade)
@ -49,10 +48,8 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh
} }
override fun initView() { override fun initView() {
gradeSummaryAdapter.setDisplayHeadersAtStartUp(true)
gradeSummaryRecycler.run { gradeSummaryRecycler.run {
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
adapter = gradeSummaryAdapter adapter = gradeSummaryAdapter
} }
gradeSummarySwipe.setOnRefreshListener { presenter.onSwipeRefresh() } gradeSummarySwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
@ -60,16 +57,18 @@ class GradeSummaryFragment : BaseFragment(), GradeSummaryView, GradeView.GradeCh
gradeSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() } gradeSummaryErrorDetails.setOnClickListener { presenter.onDetailsClick() }
} }
override fun updateData(data: List<GradeSummaryItem>, header: GradeSummaryScrollableHeader) { override fun updateData(data: List<GradeSummary>) {
gradeSummaryAdapter.apply { with(gradeSummaryAdapter) {
updateDataSet(data, true) items = data
removeAllScrollableHeaders() notifyDataSetChanged()
addScrollableHeader(header)
} }
} }
override fun clearView() { override fun clearView() {
gradeSummaryAdapter.clear() with(gradeSummaryAdapter) {
items = emptyList()
notifyDataSetChanged()
}
} }
override fun resetView() { override fun resetView() {

View File

@ -1,64 +0,0 @@
package io.github.wulkanowy.ui.modules.grade.summary
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.GradeSummary
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_grade_summary.*
class GradeSummaryItem(
val summary: GradeSummary,
private val average: String
) : AbstractFlexibleItem<GradeSummaryItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_grade_summary
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
return ViewHolder(view, adapter)
}
@SuppressLint("SetTextI18n")
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
holder.run {
gradeSummaryItemTitle.text = summary.subject
gradeSummaryItemPoints.text = summary.pointsSum
gradeSummaryItemAverage.text = average
gradeSummaryItemPredicted.text = "${summary.predictedGrade} ${summary.proposedPoints}".trim()
gradeSummaryItemFinal.text = "${summary.finalGrade} ${summary.finalPoints}".trim()
gradeSummaryItemPointsContainer.visibility = if (summary.pointsSum.isBlank()) GONE else VISIBLE
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as GradeSummaryItem
if (average != other.average) return false
if (summary != other.summary) return false
if (summary.id != other.summary.id) return false
return true
}
override fun hashCode(): Int {
var result = summary.hashCode()
result = 31 * result + summary.id.hashCode()
result = 31 * result + average.hashCode()
return result
}
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = contentView
}
}

View File

@ -9,10 +9,7 @@ import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider import io.github.wulkanowy.ui.modules.grade.GradeAverageProvider
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.calcAverage
import timber.log.Timber import timber.log.Timber
import java.lang.String.format
import java.util.Locale.FRANCE
import javax.inject.Inject import javax.inject.Inject
class GradeSummaryPresenter @Inject constructor( class GradeSummaryPresenter @Inject constructor(
@ -42,7 +39,7 @@ class GradeSummaryPresenter @Inject constructor(
.map { it.sortedBy { subject -> subject.subject } } .map { it.sortedBy { subject -> subject.subject } }
.flatMap { gradesSummary -> .flatMap { gradesSummary ->
averageProvider.getGradeAverage(student, semesters, semesterId, forceRefresh) averageProvider.getGradeAverage(student, semesters, semesterId, forceRefresh)
.map { averages -> createGradeSummaryItemsAndHeader(gradesSummary, averages) } .map { averages -> createGradeSummaryItems(gradesSummary, averages) }
} }
} }
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
@ -54,15 +51,15 @@ class GradeSummaryPresenter @Inject constructor(
enableSwipe(true) enableSwipe(true)
notifyParentDataLoaded(semesterId) notifyParentDataLoaded(semesterId)
} }
}.subscribe({ (gradeSummaryItems, gradeSummaryHeader) -> }.subscribe({
Timber.i("Loading grade summary result: Success") Timber.i("Loading grade summary result: Success")
view?.run { view?.run {
showEmpty(gradeSummaryItems.isEmpty()) showEmpty(it.isEmpty())
showContent(gradeSummaryItems.isNotEmpty()) showContent(it.isNotEmpty())
showErrorView(false) showErrorView(false)
updateData(gradeSummaryItems, gradeSummaryHeader) updateData(it)
} }
analytics.logEvent("load_grade_summary", "items" to gradeSummaryItems.size, "force_refresh" to forceRefresh) analytics.logEvent("load_grade_summary", "items" to it.size, "force_refresh" to forceRefresh)
}) { }) {
Timber.i("Loading grade summary result: An exception occurred") Timber.i("Loading grade summary result: An exception occurred")
errorHandler.dispatch(it) errorHandler.dispatch(it)
@ -115,20 +112,11 @@ class GradeSummaryPresenter @Inject constructor(
disposable.clear() disposable.clear()
} }
private fun createGradeSummaryItemsAndHeader(gradesSummary: List<GradeSummary>, averages: List<Triple<String, Double, String>>): Pair<List<GradeSummaryItem>, GradeSummaryScrollableHeader> { private fun createGradeSummaryItems(gradesSummary: List<GradeSummary>, averages: List<Triple<String, Double, String>>): List<GradeSummary> {
return averages.filter { value -> value.second != 0.0 } return gradesSummary
.let { filteredAverages -> .filter { !checkEmpty(it, averages) }
gradesSummary.filter { !checkEmpty(it, filteredAverages) }
.map { gradeSummary -> .map { gradeSummary ->
GradeSummaryItem( gradeSummary.copy(average = averages.singleOrNull { gradeSummary.subject == it.first }?.second ?: .0)
summary = gradeSummary,
average = formatAverage(filteredAverages.singleOrNull { gradeSummary.subject == it.first }?.second ?: .0, "")
)
}.let {
it to GradeSummaryScrollableHeader(
formatAverage(gradesSummary.calcAverage()),
formatAverage(filteredAverages.map { values -> values.second }.average()))
}
} }
} }
@ -137,9 +125,4 @@ class GradeSummaryPresenter @Inject constructor(
finalGrade.isBlank() && predictedGrade.isBlank() && averages.singleOrNull { it.first == subject } == null finalGrade.isBlank() && predictedGrade.isBlank() && averages.singleOrNull { it.first == subject } == null
} }
} }
private fun formatAverage(average: Double, defaultValue: String = "-- --"): String {
return if (average == 0.0) defaultValue
else format(FRANCE, "%.2f", average)
}
} }

View File

@ -1,53 +0,0 @@
package io.github.wulkanowy.ui.modules.grade.summary
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 kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.scrollable_header_grade_summary.*
class GradeSummaryScrollableHeader(private val finalAverage: String, private val calculatedAverage: String)
: AbstractFlexibleItem<GradeSummaryScrollableHeader.ViewHolder>() {
override fun getLayoutRes() = R.layout.scrollable_header_grade_summary
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?): ViewHolder {
return ViewHolder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>?, holder: ViewHolder?,
position: Int, payloads: MutableList<Any>?) {
holder?.apply {
gradeSummaryScrollableHeaderFinal.text = finalAverage
gradeSummaryScrollableHeaderCalculated.text = calculatedAverage
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as GradeSummaryScrollableHeader
if (calculatedAverage != other.calculatedAverage) return false
if (finalAverage != other.finalAverage) return false
return true
}
override fun hashCode(): Int {
var result = calculatedAverage.hashCode()
result = 31 * result + finalAverage.hashCode()
return result
}
class ViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?) : FlexibleViewHolder(view, adapter),
LayoutContainer {
override val containerView: View?
get() = contentView
}
}

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.grade.summary package io.github.wulkanowy.ui.modules.grade.summary
import io.github.wulkanowy.data.db.entities.GradeSummary
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
interface GradeSummaryView : BaseView { interface GradeSummaryView : BaseView {
@ -12,7 +13,7 @@ interface GradeSummaryView : BaseView {
fun initView() fun initView()
fun updateData(data: List<GradeSummaryItem>, header: GradeSummaryScrollableHeader) fun updateData(data: List<GradeSummary>)
fun resetView() fun resetView()

View File

@ -0,0 +1,67 @@
package io.github.wulkanowy.ui.modules.homework
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.databinding.HeaderHomeworkBinding
import io.github.wulkanowy.databinding.ItemHomeworkBinding
import io.github.wulkanowy.utils.toFormattedString
import io.github.wulkanowy.utils.weekDayName
import org.threeten.bp.LocalDate
import javax.inject.Inject
class HomeworkAdapter @Inject constructor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var items = emptyList<HomeworkItem<*>>()
var onClickListener: (Homework) -> Unit = {}
override fun getItemCount() = items.size
override fun getItemViewType(position: Int) = items[position].viewType.id
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
HomeworkItem.ViewType.HEADER.id -> HeaderViewHolder(HeaderHomeworkBinding.inflate(inflater, parent, false))
HomeworkItem.ViewType.ITEM.id -> ItemViewHolder(ItemHomeworkBinding.inflate(inflater, parent, false))
else -> throw IllegalStateException()
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is HeaderViewHolder -> bindHeaderViewHolder(holder.binding, items[position].value as LocalDate)
is ItemViewHolder -> bindItemViewHolder(holder.binding, items[position].value as Homework)
}
}
@SuppressLint("DefaultLocale")
private fun bindHeaderViewHolder(binding: HeaderHomeworkBinding, date: LocalDate) {
with(binding) {
homeworkHeaderDay.text = date.weekDayName.capitalize()
homeworkHeaderDate.text = date.toFormattedString()
}
}
private fun bindItemViewHolder(binding: ItemHomeworkBinding, homework: Homework) {
with(binding) {
homeworkItemSubject.text = homework.subject
homeworkItemTeacher.text = homework.teacher
homeworkItemContent.text = homework.content
homeworkItemCheckImage.visibility = if (homework.isDone) View.VISIBLE else View.GONE
homeworkItemAttachmentImage.visibility = if (!homework.isDone && homework.attachments.isNotEmpty()) View.VISIBLE else View.GONE
root.setOnClickListener { onClickListener(homework) }
}
}
class HeaderViewHolder(val binding: HeaderHomeworkBinding) :
RecyclerView.ViewHolder(binding.root)
class ItemViewHolder(val binding: ItemHomeworkBinding) : RecyclerView.ViewHolder(binding.root)
}

View File

@ -6,18 +6,15 @@ import android.view.View
import android.view.View.GONE import android.view.View.GONE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter import androidx.recyclerview.widget.LinearLayoutManager
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.R
import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import io.github.wulkanowy.ui.modules.homework.details.HomeworkDetailsDialog import io.github.wulkanowy.ui.modules.homework.details.HomeworkDetailsDialog
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.dpToPx import io.github.wulkanowy.utils.dpToPx
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_homework.* import kotlinx.android.synthetic.main.fragment_homework.*
import javax.inject.Inject import javax.inject.Inject
@ -27,7 +24,7 @@ class HomeworkFragment : BaseFragment(), HomeworkView, MainView.TitledView {
lateinit var presenter: HomeworkPresenter lateinit var presenter: HomeworkPresenter
@Inject @Inject
lateinit var homeworkAdapter: FlexibleAdapter<AbstractFlexibleItem<*>> lateinit var homeworkAdapter: HomeworkAdapter
companion object { companion object {
private const val SAVED_DATE_KEY = "CURRENT_DATE" private const val SAVED_DATE_KEY = "CURRENT_DATE"
@ -37,7 +34,7 @@ class HomeworkFragment : BaseFragment(), HomeworkView, MainView.TitledView {
override val titleStringId get() = R.string.homework_title override val titleStringId get() = R.string.homework_title
override val isViewEmpty get() = homeworkAdapter.isEmpty override val isViewEmpty get() = homeworkAdapter.items.isEmpty()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_homework, container, false) return inflater.inflate(R.layout.fragment_homework, container, false)
@ -50,14 +47,12 @@ class HomeworkFragment : BaseFragment(), HomeworkView, MainView.TitledView {
} }
override fun initView() { override fun initView() {
homeworkAdapter.setOnItemClickListener(presenter::onHomeworkItemSelected) homeworkAdapter.onClickListener = presenter::onHomeworkItemSelected
with(homeworkRecycler) { with(homeworkRecycler) {
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
adapter = homeworkAdapter adapter = homeworkAdapter
addItemDecoration(FlexibleItemDecoration(context) addItemDecoration(DividerItemDecoration(context))
.withDefaultDivider()
.withDrawDividerOnLastItem(false))
} }
homeworkSwipe.setOnRefreshListener(presenter::onSwipeRefresh) homeworkSwipe.setOnRefreshListener(presenter::onSwipeRefresh)
@ -70,8 +65,11 @@ class HomeworkFragment : BaseFragment(), HomeworkView, MainView.TitledView {
homeworkNavContainer.setElevationCompat(requireContext().dpToPx(8f)) homeworkNavContainer.setElevationCompat(requireContext().dpToPx(8f))
} }
override fun updateData(data: List<HomeworkItem>) { override fun updateData(data: List<HomeworkItem<*>>) {
homeworkAdapter.updateDataSet(data, true) with(homeworkAdapter) {
items = data
notifyDataSetChanged()
}
} }
fun onReloadList() { fun onReloadList() {
@ -79,7 +77,10 @@ class HomeworkFragment : BaseFragment(), HomeworkView, MainView.TitledView {
} }
override fun clearData() { override fun clearData() {
homeworkAdapter.clear() with(homeworkAdapter) {
items = emptyList()
notifyDataSetChanged()
}
} }
override fun updateNavigationWeek(date: String) { override fun updateNavigationWeek(date: String) {

View File

@ -1,54 +0,0 @@
package io.github.wulkanowy.ui.modules.homework
import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractHeaderItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.ExpandableViewHolder
import io.github.wulkanowy.R
import io.github.wulkanowy.utils.toFormattedString
import io.github.wulkanowy.utils.weekDayName
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.header_homework.*
import org.threeten.bp.LocalDate
class HomeworkHeader(private val date: LocalDate) : AbstractHeaderItem<HomeworkHeader.ViewHolder>() {
override fun getLayoutRes() = R.layout.header_homework
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?): ViewHolder {
return ViewHolder(view, adapter)
}
override fun bindViewHolder(
adapter: FlexibleAdapter<IFlexible<*>>?, holder: HomeworkHeader.ViewHolder,
position: Int, payloads: MutableList<Any>?
) {
holder.run {
homeworkHeaderDay.text = date.weekDayName.capitalize()
homeworkHeaderDate.text = date.toFormattedString()
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as HomeworkHeader
if (date != other.date) return false
return true
}
override fun hashCode(): Int {
return date.hashCode()
}
class ViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>>?) : ExpandableViewHolder(view, adapter),
LayoutContainer {
override val containerView: View
get() = contentView
}
}

View File

@ -1,53 +1,9 @@
package io.github.wulkanowy.ui.modules.homework package io.github.wulkanowy.ui.modules.homework
import android.view.View data class HomeworkItem<out T>(val value: T, val viewType: ViewType) {
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractSectionableItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Homework
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_homework.*
class HomeworkItem(header: HomeworkHeader, val homework: Homework) : enum class ViewType(val id: Int) {
AbstractSectionableItem<HomeworkItem.ViewHolder, HomeworkHeader>(header) { HEADER(1),
ITEM(2)
override fun getLayoutRes() = R.layout.item_homework
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
return ViewHolder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
holder.apply {
homeworkItemSubject.text = homework.subject
homeworkItemTeacher.text = homework.teacher
homeworkItemContent.text = homework.content
homeworkItemCheckImage.visibility = if (homework.isDone) View.VISIBLE else View.GONE
homeworkItemAttachmentImage.visibility = if (!homework.isDone && homework.attachments.isNotEmpty()) View.VISIBLE else View.GONE
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as HomeworkItem
if (homework != other.homework) return false
return true
}
override fun hashCode(): Int {
var result = homework.hashCode()
result = 31 * result + homework.id.toInt()
return result
}
class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) :
FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = contentView
} }
} }

View File

@ -1,6 +1,5 @@
package io.github.wulkanowy.ui.modules.homework package io.github.wulkanowy.ui.modules.homework
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.db.entities.Homework import io.github.wulkanowy.data.db.entities.Homework
import io.github.wulkanowy.data.repositories.homework.HomeworkRepository import io.github.wulkanowy.data.repositories.homework.HomeworkRepository
import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository
@ -18,7 +17,6 @@ import io.github.wulkanowy.utils.toFormattedString
import org.threeten.bp.LocalDate import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDate.ofEpochDay import org.threeten.bp.LocalDate.ofEpochDay
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
class HomeworkPresenter @Inject constructor( class HomeworkPresenter @Inject constructor(
@ -74,11 +72,9 @@ class HomeworkPresenter @Inject constructor(
view?.showErrorDetailsDialog(lastError) view?.showErrorDetailsDialog(lastError)
} }
fun onHomeworkItemSelected(item: AbstractFlexibleItem<*>?) { fun onHomeworkItemSelected(homework: Homework) {
if (item is HomeworkItem) { Timber.i("Select homework item ${homework.id}")
Timber.i("Select homework item ${item.homework.id}") view?.showTimetableDialog(homework)
view?.showTimetableDialog(item.homework)
}
} }
private fun setBaseDateOnHolidays() { private fun setBaseDateOnHolidays() {
@ -110,8 +106,6 @@ class HomeworkPresenter @Inject constructor(
homeworkRepository.getHomework(student, semester, currentDate, currentDate, forceRefresh) homeworkRepository.getHomework(student, semester, currentDate, currentDate, forceRefresh)
} }
} }
.delay(200, TimeUnit.MILLISECONDS)
.map { it.groupBy { homework -> homework.date }.toSortedMap() }
.map { createHomeworkItem(it) } .map { createHomeworkItem(it) }
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
@ -150,12 +144,12 @@ class HomeworkPresenter @Inject constructor(
} }
} }
private fun createHomeworkItem(items: Map<LocalDate, List<Homework>>): List<HomeworkItem> { private fun createHomeworkItem(items: List<Homework>): List<HomeworkItem<*>> {
return items.flatMap { return items.groupBy { it.date }.toSortedMap().map { (date, exams) ->
HomeworkHeader(it.key).let { header -> listOf(HomeworkItem(date, HomeworkItem.ViewType.HEADER)) + exams.reversed().map { exam ->
it.value.reversed().map { item -> HomeworkItem(header, item) } HomeworkItem(exam, HomeworkItem.ViewType.ITEM)
}
} }
}.flatten()
} }
private fun reloadView() { private fun reloadView() {

View File

@ -9,7 +9,7 @@ interface HomeworkView : BaseView {
fun initView() fun initView()
fun updateData(data: List<HomeworkItem>) fun updateData(data: List<HomeworkItem<*>>)
fun clearData() fun clearData()

View File

@ -0,0 +1,51 @@
package io.github.wulkanowy.ui.modules.login.studentselect
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.databinding.ItemLoginStudentSelectBinding
import javax.inject.Inject
class LoginStudentSelectAdapter @Inject constructor() :
RecyclerView.Adapter<LoginStudentSelectAdapter.ItemViewHolder>() {
var items = emptyList<Pair<Student, Boolean>>()
var onClickListener: (Student, alreadySaved: Boolean) -> Unit = { _, _ -> }
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
ItemLoginStudentSelectBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val (student, alreadySaved) = items[position]
with(holder.binding) {
loginItemName.text = "${student.studentName} ${student.className}"
loginItemSchool.text = student.schoolName
loginItemName.isEnabled = !alreadySaved
loginItemSchool.isEnabled = !alreadySaved
loginItemCheck.isEnabled = !alreadySaved
loginItemSignedIn.visibility = if (alreadySaved) View.VISIBLE else View.GONE
root.setOnClickListener {
onClickListener(student, alreadySaved)
with(loginItemCheck) {
if (isEnabled) {
isChecked = !isChecked
}
}
}
}
}
class ItemViewHolder(val binding: ItemLoginStudentSelectBinding) :
RecyclerView.ViewHolder(binding.root)
}

View File

@ -6,9 +6,7 @@ import android.view.View
import android.view.View.GONE import android.view.View.GONE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter import androidx.recyclerview.widget.LinearLayoutManager
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
@ -16,7 +14,6 @@ import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.utils.AppInfo import io.github.wulkanowy.utils.AppInfo
import io.github.wulkanowy.utils.openEmailClient import io.github.wulkanowy.utils.openEmailClient
import io.github.wulkanowy.utils.openInternetBrowser import io.github.wulkanowy.utils.openInternetBrowser
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_login_student_select.* import kotlinx.android.synthetic.main.fragment_login_student_select.*
import java.io.Serializable import java.io.Serializable
import javax.inject.Inject import javax.inject.Inject
@ -27,7 +24,7 @@ class LoginStudentSelectFragment : BaseFragment(), LoginStudentSelectView {
lateinit var presenter: LoginStudentSelectPresenter lateinit var presenter: LoginStudentSelectPresenter
@Inject @Inject
lateinit var loginAdapter: FlexibleAdapter<AbstractFlexibleItem<*>> lateinit var loginAdapter: LoginStudentSelectAdapter
@Inject @Inject
lateinit var appInfo: AppInfo lateinit var appInfo: AppInfo
@ -48,19 +45,22 @@ class LoginStudentSelectFragment : BaseFragment(), LoginStudentSelectView {
} }
override fun initView() { override fun initView() {
loginAdapter.onClickListener = presenter::onItemSelected
loginStudentSelectSignIn.setOnClickListener { presenter.onSignIn() } loginStudentSelectSignIn.setOnClickListener { presenter.onSignIn() }
loginAdapter.apply { setOnItemClickListener { presenter.onItemSelected(it) } }
loginStudentSelectContactDiscord.setOnClickListener { presenter.onDiscordClick() } loginStudentSelectContactDiscord.setOnClickListener { presenter.onDiscordClick() }
loginStudentSelectContactEmail.setOnClickListener { presenter.onEmailClick() } loginStudentSelectContactEmail.setOnClickListener { presenter.onEmailClick() }
loginStudentSelectRecycler.apply { loginStudentSelectRecycler.apply {
layoutManager = LinearLayoutManager(context)
adapter = loginAdapter adapter = loginAdapter
layoutManager = SmoothScrollLinearLayoutManager(context)
} }
} }
override fun updateData(data: List<LoginStudentSelectItem>) { override fun updateData(data: List<Pair<Student, Boolean>>) {
loginAdapter.updateDataSet(data) with(loginAdapter) {
items = data
notifyDataSetChanged()
}
} }
override fun openMainView() { override fun openMainView() {

View File

@ -1,71 +0,0 @@
package io.github.wulkanowy.ui.modules.login.studentselect
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.Student
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_login_student_select.*
class LoginStudentSelectItem(val student: Student, val alreadySaved: Boolean) :
AbstractFlexibleItem<LoginStudentSelectItem.ItemViewHolder>() {
override fun getLayoutRes() = R.layout.item_login_student_select
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ItemViewHolder {
return ItemViewHolder(view, adapter)
}
@SuppressLint("SetTextI18n")
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ItemViewHolder, position: Int, payloads: MutableList<Any>) {
holder.apply {
loginItemName.text = "${student.studentName} ${student.className}"
loginItemSchool.text = student.schoolName
loginItemName.isEnabled = !alreadySaved
loginItemSchool.isEnabled = !alreadySaved
loginItemCheck.isEnabled = !alreadySaved
loginItemSignedIn.visibility = if (alreadySaved) VISIBLE else GONE
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as LoginStudentSelectItem
if (student != other.student) return false
if (alreadySaved != other.alreadySaved) return false
return true
}
override fun hashCode(): Int {
return student.hashCode()
}
class ItemViewHolder(view: View, val adapter: FlexibleAdapter<*>) :
FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = itemView
init {
loginItemCheck.keyListener = null
}
override fun onClick(view: View?) {
super.onClick(view)
if (loginItemCheck.isEnabled) {
loginItemCheck.apply { isChecked = !isChecked }
}
}
}
}

View File

@ -1,6 +1,5 @@
package io.github.wulkanowy.ui.modules.login.studentselect package io.github.wulkanowy.ui.modules.login.studentselect
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
@ -51,14 +50,15 @@ class LoginStudentSelectPresenter @Inject constructor(
if (students.size == 1) registerStudents(students) if (students.size == 1) registerStudents(students)
} }
fun onItemSelected(item: AbstractFlexibleItem<*>?) { fun onItemSelected(student: Student, alreadySaved: Boolean) {
if (item is LoginStudentSelectItem && !item.alreadySaved) { if (alreadySaved) return
selectedStudents.removeAll { it == item.student }
.let { if (!it) selectedStudents.add(item.student) } selectedStudents
.removeAll { it == student }
.let { if (!it) selectedStudents.add(student) }
view?.enableSignIn(selectedStudents.isNotEmpty()) view?.enableSignIn(selectedStudents.isNotEmpty())
} }
}
private fun compareStudents(a: Student, b: Student): Boolean { private fun compareStudents(a: Student, b: Student): Boolean {
return a.email == b.email return a.email == b.email
@ -73,19 +73,17 @@ class LoginStudentSelectPresenter @Inject constructor(
disposable.add(studentRepository.getSavedStudents() disposable.add(studentRepository.getSavedStudents()
.map { savedStudents -> .map { savedStudents ->
students.map { student -> students.map { student ->
Pair(student, savedStudents.any { compareStudents(student, it) }) student to savedStudents.any { compareStudents(student, it) }
} }
} }
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
.subscribe({ .subscribe({
view?.updateData(it.map { studentPair -> view?.updateData(it)
LoginStudentSelectItem(studentPair.first, studentPair.second)
})
}, { }, {
errorHandler.dispatch(it) errorHandler.dispatch(it)
lastError = it lastError = it
view?.updateData(students.map { student -> LoginStudentSelectItem(student, false) }) view?.updateData(students.map { student -> student to false })
}) })
) )
} }

View File

@ -1,12 +1,13 @@
package io.github.wulkanowy.ui.modules.login.studentselect package io.github.wulkanowy.ui.modules.login.studentselect
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
interface LoginStudentSelectView : BaseView { interface LoginStudentSelectView : BaseView {
fun initView() fun initView()
fun updateData(data: List<LoginStudentSelectItem>) fun updateData(data: List<Pair<Student, Boolean>>)
fun openMainView() fun openMainView()

View File

@ -7,13 +7,12 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import eu.davidea.flexibleadapter.FlexibleAdapter import androidx.recyclerview.widget.LinearLayoutManager
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BaseActivity import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.ui.modules.login.LoginActivity import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.utils.setOnItemClickListener import io.github.wulkanowy.ui.base.WidgetConfigureAdapter
import kotlinx.android.synthetic.main.activity_widget_configure.* import kotlinx.android.synthetic.main.activity_widget_configure.*
import javax.inject.Inject import javax.inject.Inject
@ -21,7 +20,7 @@ class LuckyNumberWidgetConfigureActivity : BaseActivity<LuckyNumberWidgetConfigu
LuckyNumberWidgetConfigureView { LuckyNumberWidgetConfigureView {
@Inject @Inject
lateinit var configureAdapter: FlexibleAdapter<AbstractFlexibleItem<*>> lateinit var configureAdapter: WidgetConfigureAdapter
@Inject @Inject
override lateinit var presenter: LuckyNumberWidgetConfigurePresenter override lateinit var presenter: LuckyNumberWidgetConfigurePresenter
@ -41,10 +40,10 @@ class LuckyNumberWidgetConfigureActivity : BaseActivity<LuckyNumberWidgetConfigu
override fun initView() { override fun initView() {
with(widgetConfigureRecycler) { with(widgetConfigureRecycler) {
adapter = configureAdapter adapter = configureAdapter
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
} }
configureAdapter.setOnItemClickListener(presenter::onItemSelect) configureAdapter.onClickListener = presenter::onItemSelect
} }
override fun showThemeDialog() { override fun showThemeDialog() {
@ -62,8 +61,11 @@ class LuckyNumberWidgetConfigureActivity : BaseActivity<LuckyNumberWidgetConfigu
.show() .show()
} }
override fun updateData(data: List<LuckyNumberWidgetConfigureItem>) { override fun updateData(data: List<Pair<Student, Boolean>>) {
configureAdapter.updateDataSet(data) with(configureAdapter) {
items = data
notifyDataSetChanged()
}
} }
override fun updateLuckyNumberWidget(widgetId: Int) { override fun updateLuckyNumberWidget(widgetId: Int) {

View File

@ -1,60 +0,0 @@
package io.github.wulkanowy.ui.modules.luckynumberwidget
import android.annotation.SuppressLint
import android.view.View
import androidx.core.graphics.ColorUtils
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.Student
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetConfigureItem
import io.github.wulkanowy.utils.getThemeAttrColor
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_account.*
class LuckyNumberWidgetConfigureItem(var student: Student, val isCurrent: Boolean) :
AbstractFlexibleItem<LuckyNumberWidgetConfigureItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_account
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>) = ViewHolder(view, adapter)
@SuppressLint("SetTextI18n")
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
val context = holder.contentView.context
val colorImage = if (isCurrent) context.getThemeAttrColor(R.attr.colorPrimary)
else ColorUtils.setAlphaComponent(context.getThemeAttrColor(R.attr.colorOnSurface), 153)
with(holder) {
accountItemName.text = "${student.studentName} ${student.className}"
accountItemSchool.text = student.schoolName
accountItemImage.setColorFilter(colorImage)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as TimetableWidgetConfigureItem
if (student != other.student) return false
return true
}
override fun hashCode(): Int {
var result = student.hashCode()
result = 31 * result + student.id.toInt()
return result
}
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter),
LayoutContainer {
override val containerView: View? get() = contentView
}
}

View File

@ -1,6 +1,5 @@
package io.github.wulkanowy.ui.modules.luckynumberwidget package io.github.wulkanowy.ui.modules.luckynumberwidget
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.student.StudentRepository
@ -29,12 +28,10 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor(
loadData() loadData()
} }
fun onItemSelect(item: AbstractFlexibleItem<*>) { fun onItemSelect(student: Student) {
if (item is LuckyNumberWidgetConfigureItem) { selectedStudent = student
selectedStudent = item.student
view?.showThemeDialog() view?.showThemeDialog()
} }
}
fun onThemeSelect(index: Int) { fun onThemeSelect(index: Int) {
appWidgetId?.let { appWidgetId?.let {
@ -51,7 +48,7 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor(
disposable.add(studentRepository.getSavedStudents(false) disposable.add(studentRepository.getSavedStudents(false)
.map { it to appWidgetId?.let { id -> sharedPref.getLong(getStudentWidgetKey(id), 0) } } .map { it to appWidgetId?.let { id -> sharedPref.getLong(getStudentWidgetKey(id), 0) } }
.map { (students, currentStudentId) -> .map { (students, currentStudentId) ->
students.map { student -> LuckyNumberWidgetConfigureItem(student, student.id == currentStudentId) } students.map { student -> student to (student.id == currentStudentId) }
} }
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
@ -59,7 +56,7 @@ class LuckyNumberWidgetConfigurePresenter @Inject constructor(
when { when {
it.isEmpty() -> view?.openLoginView() it.isEmpty() -> view?.openLoginView()
it.size == 1 -> { it.size == 1 -> {
selectedStudent = it.single().student selectedStudent = it.single().first
view?.showThemeDialog() view?.showThemeDialog()
} }
else -> view?.updateData(it) else -> view?.updateData(it)

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.luckynumberwidget package io.github.wulkanowy.ui.modules.luckynumberwidget
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
interface LuckyNumberWidgetConfigureView : BaseView { interface LuckyNumberWidgetConfigureView : BaseView {
@ -8,7 +9,7 @@ interface LuckyNumberWidgetConfigureView : BaseView {
fun showThemeDialog() fun showThemeDialog()
fun updateData(data: List<LuckyNumberWidgetConfigureItem>) fun updateData(data: List<Pair<Student, Boolean>>)
fun updateLuckyNumberWidget(widgetId: Int) fun updateLuckyNumberWidget(widgetId: Int)

View File

@ -9,7 +9,6 @@ import android.os.Build.VERSION_CODES.LOLLIPOP
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.core.graphics.ColorUtils
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@ -115,7 +114,7 @@ class MainActivity : BaseActivity<MainPresenter>(), MainView {
AHBottomNavigationItem(R.string.more_title, R.drawable.ic_main_more, 0) AHBottomNavigationItem(R.string.more_title, R.drawable.ic_main_more, 0)
)) ))
accentColor = getThemeAttrColor(R.attr.colorPrimary) accentColor = getThemeAttrColor(R.attr.colorPrimary)
inactiveColor = ColorUtils.setAlphaComponent(getThemeAttrColor(R.attr.colorOnSurface), 153) inactiveColor = getThemeAttrColor(R.attr.colorOnSurface, 153)
defaultBackgroundColor = overlayProvider.get().compositeOverlayWithThemeSurfaceColorIfNeeded(dpToPx(8f)) defaultBackgroundColor = overlayProvider.get().compositeOverlayWithThemeSurfaceColorIfNeeded(dpToPx(8f))
titleState = ALWAYS_SHOW titleState = ALWAYS_SHOW
currentItem = startMenuIndex currentItem = startMenuIndex

View File

@ -14,7 +14,6 @@ import io.github.wulkanowy.ui.modules.about.license.LicenseModule
import io.github.wulkanowy.ui.modules.about.logviewer.LogViewerFragment import io.github.wulkanowy.ui.modules.about.logviewer.LogViewerFragment
import io.github.wulkanowy.ui.modules.account.AccountDialog import io.github.wulkanowy.ui.modules.account.AccountDialog
import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment import io.github.wulkanowy.ui.modules.attendance.AttendanceFragment
import io.github.wulkanowy.ui.modules.attendance.AttendanceModule
import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment import io.github.wulkanowy.ui.modules.attendance.summary.AttendanceSummaryFragment
import io.github.wulkanowy.ui.modules.exam.ExamFragment import io.github.wulkanowy.ui.modules.exam.ExamFragment
import io.github.wulkanowy.ui.modules.grade.GradeFragment import io.github.wulkanowy.ui.modules.grade.GradeFragment
@ -26,7 +25,6 @@ import io.github.wulkanowy.ui.modules.message.MessageFragment
import io.github.wulkanowy.ui.modules.message.MessageModule import io.github.wulkanowy.ui.modules.message.MessageModule
import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment
import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceFragment import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceFragment
import io.github.wulkanowy.ui.modules.mobiledevice.MobileDeviceModule
import io.github.wulkanowy.ui.modules.mobiledevice.token.MobileDeviceTokenDialog import io.github.wulkanowy.ui.modules.mobiledevice.token.MobileDeviceTokenDialog
import io.github.wulkanowy.ui.modules.more.MoreFragment import io.github.wulkanowy.ui.modules.more.MoreFragment
import io.github.wulkanowy.ui.modules.note.NoteFragment import io.github.wulkanowy.ui.modules.note.NoteFragment
@ -52,7 +50,7 @@ abstract class MainModule {
} }
@PerFragment @PerFragment
@ContributesAndroidInjector(modules = [AttendanceModule::class]) @ContributesAndroidInjector
abstract fun bindAttendanceFragment(): AttendanceFragment abstract fun bindAttendanceFragment(): AttendanceFragment
@PerFragment @PerFragment
@ -116,7 +114,7 @@ abstract class MainModule {
abstract fun bindAccountDialog(): AccountDialog abstract fun bindAccountDialog(): AccountDialog
@PerFragment @PerFragment
@ContributesAndroidInjector(modules = [MobileDeviceModule::class]) @ContributesAndroidInjector
abstract fun bindMobileDevices(): MobileDeviceFragment abstract fun bindMobileDevices(): MobileDeviceFragment
@PerFragment @PerFragment

View File

@ -1,67 +0,0 @@
package io.github.wulkanowy.ui.modules.message
import android.graphics.Typeface.BOLD
import android.graphics.Typeface.NORMAL
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.Message
import io.github.wulkanowy.data.repositories.message.MessageFolder
import io.github.wulkanowy.utils.toFormattedString
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_message.*
class MessageItem(val message: Message, private val noSubjectString: String) :
AbstractFlexibleItem<MessageItem.ViewHolder>() {
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
return ViewHolder(view, adapter)
}
override fun getLayoutRes() = R.layout.item_message
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
holder.apply {
val style = if (message.unread) BOLD else NORMAL
messageItemAuthor.run {
text = if (message.folderId == MessageFolder.SENT.id) message.recipient else message.sender
setTypeface(null, style)
}
messageItemSubject.run {
text = if (message.subject.isNotBlank()) message.subject else noSubjectString
setTypeface(null, style)
}
messageItemDate.run {
text = message.date.toFormattedString()
setTypeface(null, style)
}
with(messageItemAttachmentIcon) {
visibility = if (message.hasAttachments) View.VISIBLE else View.GONE
}
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as MessageItem
if (message != other.message) return false
return true
}
override fun hashCode(): Int {
return message.hashCode()
}
class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) :
FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = contentView
}
}

View File

@ -0,0 +1,53 @@
package io.github.wulkanowy.ui.modules.message.tab
import android.graphics.Typeface
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.repositories.message.MessageFolder
import io.github.wulkanowy.databinding.ItemMessageBinding
import io.github.wulkanowy.utils.toFormattedString
import javax.inject.Inject
class MessageTabAdapter @Inject constructor() :
RecyclerView.Adapter<MessageTabAdapter.ItemViewHolder>() {
var items = mutableListOf<Message>()
var onClickListener: (Message, position: Int) -> Unit = { _, _ -> }
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
ItemMessageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val item = items[position]
with(holder.binding) {
val style = if (item.unread) Typeface.BOLD else Typeface.NORMAL
messageItemAuthor.run {
text = if (item.folderId == MessageFolder.SENT.id) item.recipient else item.sender
setTypeface(null, style)
}
messageItemSubject.run {
text = if (item.subject.isNotBlank()) item.subject else context.getString(R.string.message_no_subject)
setTypeface(null, style)
}
messageItemDate.run {
text = item.date.toFormattedString()
setTypeface(null, style)
}
messageItemAttachmentIcon.visibility = if (item.hasAttachments) View.VISIBLE else View.GONE
root.setOnClickListener { onClickListener(item, position) }
}
}
class ItemViewHolder(val binding: ItemMessageBinding) : RecyclerView.ViewHolder(binding.root)
}

View File

@ -7,19 +7,15 @@ import android.view.View.GONE
import android.view.View.INVISIBLE import android.view.View.INVISIBLE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter import androidx.recyclerview.widget.LinearLayoutManager
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.R
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.repositories.message.MessageFolder import io.github.wulkanowy.data.repositories.message.MessageFolder
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.message.MessageFragment import io.github.wulkanowy.ui.modules.message.MessageFragment
import io.github.wulkanowy.ui.modules.message.MessageItem
import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_message_tab.* import kotlinx.android.synthetic.main.fragment_message_tab.*
import javax.inject.Inject import javax.inject.Inject
@ -29,7 +25,7 @@ class MessageTabFragment : BaseFragment(), MessageTabView {
lateinit var presenter: MessageTabPresenter lateinit var presenter: MessageTabPresenter
@Inject @Inject
lateinit var tabAdapter: FlexibleAdapter<AbstractFlexibleItem<*>> lateinit var tabAdapter: MessageTabAdapter
companion object { companion object {
const val MESSAGE_TAB_FOLDER_ID = "message_tab_folder_id" const val MESSAGE_TAB_FOLDER_ID = "message_tab_folder_id"
@ -43,11 +39,8 @@ class MessageTabFragment : BaseFragment(), MessageTabView {
} }
} }
override val noSubjectString: String
get() = getString(R.string.message_no_subject)
override val isViewEmpty override val isViewEmpty
get() = tabAdapter.isEmpty get() = tabAdapter.items.isEmpty()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_message_tab, container, false) return inflater.inflate(R.layout.fragment_message_tab, container, false)
@ -62,31 +55,30 @@ class MessageTabFragment : BaseFragment(), MessageTabView {
} }
override fun initView() { override fun initView() {
tabAdapter.setOnItemClickListener { presenter.onMessageItemSelected(it) } tabAdapter.onClickListener = presenter::onMessageItemSelected
messageTabRecycler.run { messageTabRecycler.run {
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
adapter = tabAdapter adapter = tabAdapter
addItemDecoration(FlexibleItemDecoration(context) addItemDecoration(DividerItemDecoration(context))
.withDefaultDivider()
.withDrawDividerOnLastItem(false)
)
} }
messageTabSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } messageTabSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
messageTabErrorRetry.setOnClickListener { presenter.onRetry() } messageTabErrorRetry.setOnClickListener { presenter.onRetry() }
messageTabErrorDetails.setOnClickListener { presenter.onDetailsClick() } messageTabErrorDetails.setOnClickListener { presenter.onDetailsClick() }
} }
override fun updateData(data: List<MessageItem>) { override fun updateData(data: List<Message>) {
tabAdapter.updateDataSet(data) with(tabAdapter) {
items = data.toMutableList()
notifyDataSetChanged()
}
} }
override fun updateItem(item: AbstractFlexibleItem<*>) { override fun updateItem(item: Message, position: Int) {
tabAdapter.updateItem(item) with(tabAdapter) {
items[position] = item
notifyItemChanged(position)
} }
override fun clearView() {
tabAdapter.clear()
} }
override fun showProgress(show: Boolean) { override fun showProgress(show: Boolean) {

View File

@ -1,13 +1,12 @@
package io.github.wulkanowy.ui.modules.message.tab package io.github.wulkanowy.ui.modules.message.tab
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.repositories.message.MessageFolder import io.github.wulkanowy.data.repositories.message.MessageFolder
import io.github.wulkanowy.data.repositories.message.MessageRepository import io.github.wulkanowy.data.repositories.message.MessageRepository
import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.ui.modules.message.MessageItem
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider import io.github.wulkanowy.utils.SchedulersProvider
import timber.log.Timber import timber.log.Timber
@ -58,15 +57,13 @@ class MessageTabPresenter @Inject constructor(
loadData(forceRefresh) loadData(forceRefresh)
} }
fun onMessageItemSelected(item: AbstractFlexibleItem<*>) { fun onMessageItemSelected(message: Message, position: Int) {
if (item is MessageItem) { Timber.i("Select message ${message.id} item")
Timber.i("Select message ${item.message.id} item")
view?.run { view?.run {
openMessage(item.message) openMessage(message)
if (item.message.unread) { if (message.unread) {
item.message.unread = false message.unread = false
updateItem(item) updateItem(message, position)
}
} }
} }
} }
@ -79,7 +76,6 @@ class MessageTabPresenter @Inject constructor(
.flatMap { student -> .flatMap { student ->
semesterRepository.getCurrentSemester(student) semesterRepository.getCurrentSemester(student)
.flatMap { messageRepository.getMessages(student, it, folder, forceRefresh) } .flatMap { messageRepository.getMessages(student, it, folder, forceRefresh) }
.map { items -> items.map { MessageItem(it, view?.noSubjectString.orEmpty()) } }
} }
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)

View File

@ -1,23 +1,17 @@
package io.github.wulkanowy.ui.modules.message.tab package io.github.wulkanowy.ui.modules.message.tab
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.db.entities.Message import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
import io.github.wulkanowy.ui.modules.message.MessageItem
interface MessageTabView : BaseView { interface MessageTabView : BaseView {
val noSubjectString: String
val isViewEmpty: Boolean val isViewEmpty: Boolean
fun initView() fun initView()
fun updateData(data: List<MessageItem>) fun updateData(data: List<Message>)
fun updateItem(item: AbstractFlexibleItem<*>) fun updateItem(item: Message, position: Int)
fun clearView()
fun showProgress(show: Boolean) fun showProgress(show: Boolean)

View File

@ -1,10 +1,38 @@
package io.github.wulkanowy.ui.modules.mobiledevice package io.github.wulkanowy.ui.modules.mobiledevice
import eu.davidea.flexibleadapter.FlexibleAdapter import android.view.LayoutInflater
import eu.davidea.flexibleadapter.items.IFlexible import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.data.db.entities.MobileDevice import io.github.wulkanowy.data.db.entities.MobileDevice
import io.github.wulkanowy.databinding.ItemMobileDeviceBinding
import io.github.wulkanowy.utils.toFormattedString
import javax.inject.Inject
class MobileDeviceAdapter<T : IFlexible<*>> : FlexibleAdapter<T>(null, null, true) { class MobileDeviceAdapter @Inject constructor() :
RecyclerView.Adapter<MobileDeviceAdapter.ItemViewHolder>() {
var items = mutableListOf<MobileDevice>()
var onDeviceUnregisterListener: (device: MobileDevice, position: Int) -> Unit = { _, _ -> } var onDeviceUnregisterListener: (device: MobileDevice, position: Int) -> Unit = { _, _ -> }
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
ItemMobileDeviceBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val device = items[position]
with(holder.binding) {
mobileDeviceItemDate.text = device.date.toFormattedString("dd.MM.yyyy HH:mm:ss")
mobileDeviceItemName.text = device.name
mobileDeviceItemUnregister.setOnClickListener {
onDeviceUnregisterListener(device, position)
}
}
}
class ItemViewHolder(val binding: ItemMobileDeviceBinding) :
RecyclerView.ViewHolder(binding.root)
} }

View File

@ -6,13 +6,13 @@ import android.view.View
import android.view.View.GONE import android.view.View.GONE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import eu.davidea.flexibleadapter.common.FlexibleItemDecoration import androidx.core.view.postDelayed
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import eu.davidea.flexibleadapter.helpers.UndoHelper import com.google.android.material.snackbar.Snackbar
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.MobileDevice import io.github.wulkanowy.data.db.entities.MobileDevice
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.mobiledevice.token.MobileDeviceTokenDialog import io.github.wulkanowy.ui.modules.mobiledevice.token.MobileDeviceTokenDialog
@ -25,7 +25,7 @@ class MobileDeviceFragment : BaseFragment(), MobileDeviceView, MainView.TitledVi
lateinit var presenter: MobileDevicePresenter lateinit var presenter: MobileDevicePresenter
@Inject @Inject
lateinit var devicesAdapter: MobileDeviceAdapter<AbstractFlexibleItem<*>> lateinit var devicesAdapter: MobileDeviceAdapter
companion object { companion object {
fun newInstance() = MobileDeviceFragment() fun newInstance() = MobileDeviceFragment()
@ -35,7 +35,7 @@ class MobileDeviceFragment : BaseFragment(), MobileDeviceView, MainView.TitledVi
get() = R.string.mobile_devices_title get() = R.string.mobile_devices_title
override val isViewEmpty: Boolean override val isViewEmpty: Boolean
get() = devicesAdapter.isEmpty get() = devicesAdapter.items.isEmpty()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_mobile_device, container, false) return inflater.inflate(R.layout.fragment_mobile_device, container, false)
@ -48,51 +48,55 @@ class MobileDeviceFragment : BaseFragment(), MobileDeviceView, MainView.TitledVi
} }
override fun initView() { override fun initView() {
devicesAdapter.onDeviceUnregisterListener = presenter::onUnregisterDevice
with(mobileDevicesRecycler) { with(mobileDevicesRecycler) {
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
adapter = devicesAdapter adapter = devicesAdapter
addItemDecoration(FlexibleItemDecoration(context) addItemDecoration(DividerItemDecoration(context))
.withDefaultDivider()
.withDrawDividerOnLastItem(false)
)
}
with(devicesAdapter) {
isPermanentDelete = false
onDeviceUnregisterListener = presenter::onUnregisterDevice
} }
mobileDevicesSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } mobileDevicesSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
mobileDevicesErrorRetry.setOnClickListener { presenter.onRetry() } mobileDevicesErrorRetry.setOnClickListener { presenter.onRetry() }
mobileDevicesErrorDetails.setOnClickListener { presenter.onDetailsClick() } mobileDevicesErrorDetails.setOnClickListener { presenter.onDetailsClick() }
mobileDeviceAddButton.setOnClickListener { presenter.onRegisterDevice() } mobileDeviceAddButton.setOnClickListener { presenter.onRegisterDevice() }
} }
override fun updateData(data: List<MobileDeviceItem>) { override fun updateData(data: List<MobileDevice>) {
devicesAdapter.updateDataSet(data) with(devicesAdapter) {
} items = data.toMutableList()
notifyDataSetChanged()
override fun restoreDeleteItem() {
devicesAdapter.restoreDeletedItems()
}
override fun clearData() {
devicesAdapter.clear()
}
override fun showUndo(position: Int, device: MobileDevice) {
val onActionListener = object : UndoHelper.OnActionListener {
override fun onActionConfirmed(action: Int, event: Int) {
presenter.onUnregisterConfirmed(device)
}
override fun onActionCanceled(action: Int, positions: MutableList<Int>?) {
presenter.onUnregisterCancelled()
} }
} }
UndoHelper(devicesAdapter, onActionListener) override fun deleteItem(device: MobileDevice, position: Int) {
.withConsecutive(false) with(devicesAdapter) {
.withAction(UndoHelper.Action.REMOVE) items.removeAt(position)
.start(listOf(position), mobileDevicesRecycler, R.string.mobile_device_removed, R.string.all_undo, 3000) notifyItemRemoved(position)
notifyItemRangeChanged(position, itemCount)
}
}
override fun restoreDeleteItem(device: MobileDevice, position: Int) {
with(devicesAdapter) {
items.add(position, device)
notifyItemInserted(position)
notifyItemRangeChanged(position, itemCount)
}
}
override fun showUndo(device: MobileDevice, position: Int) {
var confirmed = true
Snackbar.make(mobileDevicesRecycler, getString(R.string.mobile_device_removed), 3000)
.setAction(R.string.all_undo) {
confirmed = false
presenter.onUnregisterCancelled(device, position)
}.show()
view?.postDelayed(3000) {
if (confirmed) presenter.onUnregisterConfirmed(device)
}
} }
override fun hideRefresh() { override fun hideRefresh() {

View File

@ -1,53 +0,0 @@
package io.github.wulkanowy.ui.modules.mobiledevice
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.MobileDevice
import io.github.wulkanowy.utils.toFormattedString
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_mobile_device.*
class MobileDeviceItem(val device: MobileDevice) : AbstractFlexibleItem<MobileDeviceItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_mobile_device
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
return ViewHolder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
holder.apply {
mobileDeviceItemDate.text = device.date.toFormattedString("dd.MM.yyyy HH:mm:ss")
mobileDeviceItemName.text = device.name
mobileDeviceItemUnregister.setOnClickListener {
(adapter as MobileDeviceAdapter).onDeviceUnregisterListener(device, holder.flexibleAdapterPosition)
}
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as MobileDeviceItem
if (device.id != other.device.id) return false
return true
}
override fun hashCode(): Int {
var result = device.hashCode()
result = 31 * result + device.id.toInt()
return result
}
class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = contentView
}
}

View File

@ -1,12 +0,0 @@
package io.github.wulkanowy.ui.modules.mobiledevice
import dagger.Module
import dagger.Provides
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
@Module
class MobileDeviceModule {
@Provides
fun provideMobileDeviceFlexibleAdapter() = MobileDeviceAdapter<AbstractFlexibleItem<*>>()
}

View File

@ -54,7 +54,6 @@ class MobileDevicePresenter @Inject constructor(
mobileDeviceRepository.getDevices(student, semester, forceRefresh) mobileDeviceRepository.getDevices(student, semester, forceRefresh)
} }
} }
.map { items -> items.map { MobileDeviceItem(it) } }
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
.doFinally { .doFinally {
@ -95,14 +94,15 @@ class MobileDevicePresenter @Inject constructor(
fun onUnregisterDevice(device: MobileDevice, position: Int) { fun onUnregisterDevice(device: MobileDevice, position: Int) {
view?.run { view?.run {
showUndo(position, device) deleteItem(device, position)
showUndo(device, position)
showEmpty(isViewEmpty) showEmpty(isViewEmpty)
} }
} }
fun onUnregisterCancelled() { fun onUnregisterCancelled(device: MobileDevice, position: Int) {
view?.run { view?.run {
restoreDeleteItem() restoreDeleteItem(device, position)
showEmpty(isViewEmpty) showEmpty(isViewEmpty)
} }
} }
@ -116,7 +116,6 @@ class MobileDevicePresenter @Inject constructor(
.flatMap { mobileDeviceRepository.getDevices(student, semester, it) } .flatMap { mobileDeviceRepository.getDevices(student, semester, it) }
} }
} }
.map { items -> items.map { MobileDeviceItem(it) } }
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
.doFinally { .doFinally {

View File

@ -9,14 +9,16 @@ interface MobileDeviceView : BaseView {
fun initView() fun initView()
fun updateData(data: List<MobileDeviceItem>) fun updateData(data: List<MobileDevice>)
fun restoreDeleteItem() fun deleteItem(device: MobileDevice, position: Int)
fun restoreDeleteItem(device: MobileDevice, position: Int)
fun showUndo(device: MobileDevice, position: Int)
fun hideRefresh() fun hideRefresh()
fun clearData()
fun showProgress(show: Boolean) fun showProgress(show: Boolean)
fun enableSwipe(enable: Boolean) fun enableSwipe(enable: Boolean)
@ -29,7 +31,5 @@ interface MobileDeviceView : BaseView {
fun setErrorDetails(message: String) fun setErrorDetails(message: String)
fun showUndo(position: Int, device: MobileDevice)
fun showTokenDialog() fun showTokenDialog()
} }

View File

@ -0,0 +1,34 @@
package io.github.wulkanowy.ui.modules.more
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.databinding.ItemMoreBinding
import javax.inject.Inject
class MoreAdapter @Inject constructor() : RecyclerView.Adapter<MoreAdapter.ItemViewHolder>() {
var items = emptyList<Pair<String, Drawable?>>()
var onClickListener: (name: String) -> Unit = {}
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
ItemMoreBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val (title, drawable) = items[position]
with(holder.binding) {
moreItemTitle.text = title
moreItemImage.setImageDrawable(drawable)
root.setOnClickListener { onClickListener(title) }
}
}
class ItemViewHolder(val binding: ItemMoreBinding) : RecyclerView.ViewHolder(binding.root)
}

View File

@ -5,9 +5,7 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter import androidx.recyclerview.widget.LinearLayoutManager
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.modules.about.AboutFragment import io.github.wulkanowy.ui.modules.about.AboutFragment
@ -21,7 +19,6 @@ import io.github.wulkanowy.ui.modules.note.NoteFragment
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment
import io.github.wulkanowy.ui.modules.settings.SettingsFragment import io.github.wulkanowy.ui.modules.settings.SettingsFragment
import io.github.wulkanowy.utils.getCompatDrawable import io.github.wulkanowy.utils.getCompatDrawable
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_more.* import kotlinx.android.synthetic.main.fragment_more.*
import javax.inject.Inject import javax.inject.Inject
@ -31,7 +28,7 @@ class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.Mai
lateinit var presenter: MorePresenter lateinit var presenter: MorePresenter
@Inject @Inject
lateinit var moreAdapter: FlexibleAdapter<AbstractFlexibleItem<*>> lateinit var moreAdapter: MoreAdapter
companion object { companion object {
fun newInstance() = MoreFragment() fun newInstance() = MoreFragment()
@ -74,10 +71,10 @@ class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.Mai
} }
override fun initView() { override fun initView() {
moreAdapter.setOnItemClickListener { presenter.onItemSelected(it) } moreAdapter.onClickListener = presenter::onItemSelected
moreRecycler.apply { moreRecycler.apply {
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
adapter = moreAdapter adapter = moreAdapter
} }
} }
@ -86,8 +83,11 @@ class MoreFragment : BaseFragment(), MoreView, MainView.TitledView, MainView.Mai
if (::presenter.isInitialized) presenter.onViewReselected() if (::presenter.isInitialized) presenter.onViewReselected()
} }
override fun updateData(data: List<MoreItem>) { override fun updateData(data: List<Pair<String, Drawable?>>) {
moreAdapter.updateDataSet(data) with(moreAdapter) {
items = data
notifyDataSetChanged()
}
} }
override fun openMessagesView() { override fun openMessagesView() {

View File

@ -1,47 +0,0 @@
package io.github.wulkanowy.ui.modules.more
import android.graphics.drawable.Drawable
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 kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_more.*
class MoreItem(val title: String, private val drawable: Drawable?) : AbstractFlexibleItem<MoreItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_more
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
return ViewHolder(view, adapter)
}
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
holder.apply {
moreItemTitle.text = title
moreItemImage.setImageDrawable(drawable)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as MoreItem
if (title != other.title) return false
return true
}
override fun hashCode(): Int {
return title.hashCode()
}
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = contentView
}
}

View File

@ -1,6 +1,5 @@
package io.github.wulkanowy.ui.modules.more package io.github.wulkanowy.ui.modules.more
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.repositories.student.StudentRepository import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.BasePresenter import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
@ -21,11 +20,10 @@ class MorePresenter @Inject constructor(
loadData() loadData()
} }
fun onItemSelected(item: AbstractFlexibleItem<*>?) { fun onItemSelected(title: String) {
if (item !is MoreItem) return Timber.i("Select more item \"${title}\"")
Timber.i("Select more item \"${item.title}\"")
view?.run { view?.run {
when (item.title) { when (title) {
messagesRes?.first -> openMessagesView() messagesRes?.first -> openMessagesView()
homeworkRes?.first -> openHomeworkView() homeworkRes?.first -> openHomeworkView()
noteRes?.first -> openNoteView() noteRes?.first -> openNoteView()
@ -47,15 +45,15 @@ class MorePresenter @Inject constructor(
Timber.i("Load items for more view") Timber.i("Load items for more view")
view?.run { view?.run {
updateData(listOfNotNull( updateData(listOfNotNull(
messagesRes?.let { MoreItem(it.first, it.second) }, messagesRes,
homeworkRes?.let { MoreItem(it.first, it.second) }, homeworkRes,
noteRes?.let { MoreItem(it.first, it.second) }, noteRes,
luckyNumberRes?.let { MoreItem(it.first, it.second) }, luckyNumberRes,
mobileDevicesRes?.let { MoreItem(it.first, it.second) }, mobileDevicesRes,
schoolAndTeachersRes?.let { MoreItem(it.first, it.second) }, schoolAndTeachersRes,
settingsRes?.let { MoreItem(it.first, it.second) }, settingsRes,
aboutRes?.let { MoreItem(it.first, it.second) }) aboutRes
) ))
} }
} }
} }

View File

@ -23,7 +23,7 @@ interface MoreView : BaseView {
fun initView() fun initView()
fun updateData(data: List<MoreItem>) fun updateData(data: List<Pair<String, Drawable?>>)
fun openSettingsView() fun openSettingsView()

View File

@ -0,0 +1,60 @@
package io.github.wulkanowy.ui.modules.note
import android.annotation.SuppressLint
import android.graphics.Typeface
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import io.github.wulkanowy.sdk.scrapper.notes.Note.CategoryType
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Note
import io.github.wulkanowy.databinding.ItemNoteBinding
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.toFormattedString
import javax.inject.Inject
class NoteAdapter @Inject constructor() : RecyclerView.Adapter<NoteAdapter.ItemViewHolder>() {
var items = mutableListOf<Note>()
var onClickListener: (Note, position: Int) -> Unit = { _, _ -> }
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
ItemNoteBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val item = items[position]
with(holder.binding) {
with(noteItemDate) {
text = item.date.toFormattedString()
setTypeface(null, if (item.isRead) Typeface.NORMAL else Typeface.BOLD)
}
with(noteItemType) {
text = item.category
setTypeface(null, if (item.isRead) Typeface.NORMAL else Typeface.BOLD)
}
with(noteItemPoints) {
text = "${if (item.points > 0) "+" else ""}${item.points}"
visibility = if (item.isPointsShow) View.VISIBLE else View.GONE
setTextColor(when (CategoryType.getByValue(item.categoryType)) {
CategoryType.POSITIVE -> ContextCompat.getColor(context, R.color.note_positive)
CategoryType.NEGATIVE -> ContextCompat.getColor(context, R.color.note_negative)
else -> context.getThemeAttrColor(android.R.attr.textColorPrimary)
})
}
noteItemTeacher.text = item.teacher
noteItemContent.text = item.content
root.setOnClickListener { onClickListener(item, position) }
}
}
class ItemViewHolder(val binding: ItemNoteBinding) : RecyclerView.ViewHolder(binding.root)
}

View File

@ -6,16 +6,13 @@ import android.view.View
import android.view.View.GONE import android.view.View.GONE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter import androidx.recyclerview.widget.LinearLayoutManager
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.R
import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.db.entities.Note
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import io.github.wulkanowy.ui.modules.main.MainActivity import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.setOnItemClickListener
import kotlinx.android.synthetic.main.fragment_note.* import kotlinx.android.synthetic.main.fragment_note.*
import javax.inject.Inject import javax.inject.Inject
@ -25,7 +22,7 @@ class NoteFragment : BaseFragment(), NoteView, MainView.TitledView {
lateinit var presenter: NotePresenter lateinit var presenter: NotePresenter
@Inject @Inject
lateinit var noteAdapter: FlexibleAdapter<AbstractFlexibleItem<*>> lateinit var noteAdapter: NoteAdapter
companion object { companion object {
fun newInstance() = NoteFragment() fun newInstance() = NoteFragment()
@ -35,7 +32,7 @@ class NoteFragment : BaseFragment(), NoteView, MainView.TitledView {
get() = R.string.note_title get() = R.string.note_title
override val isViewEmpty: Boolean override val isViewEmpty: Boolean
get() = noteAdapter.isEmpty get() = noteAdapter.items.isEmpty()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_note, container, false) return inflater.inflate(R.layout.fragment_note, container, false)
@ -47,17 +44,12 @@ class NoteFragment : BaseFragment(), NoteView, MainView.TitledView {
} }
override fun initView() { override fun initView() {
noteAdapter.run { noteAdapter.onClickListener = presenter::onNoteItemSelected
setOnItemClickListener { presenter.onNoteItemSelected(it) }
}
noteRecycler.run { noteRecycler.run {
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
adapter = noteAdapter adapter = noteAdapter
addItemDecoration(FlexibleItemDecoration(context) addItemDecoration(DividerItemDecoration(context))
.withDefaultDivider()
.withDrawDividerOnLastItem(false)
)
} }
noteSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } noteSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
noteErrorRetry.setOnClickListener { presenter.onRetry() } noteErrorRetry.setOnClickListener { presenter.onRetry() }
@ -68,16 +60,25 @@ class NoteFragment : BaseFragment(), NoteView, MainView.TitledView {
(activity as? MainActivity)?.showDialogFragment(NoteDialog.newInstance(note)) (activity as? MainActivity)?.showDialogFragment(NoteDialog.newInstance(note))
} }
override fun updateData(data: List<NoteItem>) { override fun updateData(data: List<Note>) {
noteAdapter.updateDataSet(data, true) with(noteAdapter) {
items = data.toMutableList()
notifyDataSetChanged()
}
} }
override fun updateItem(item: AbstractFlexibleItem<*>) { override fun updateItem(item: Note, position: Int) {
noteAdapter.updateItem(item) with(noteAdapter) {
items[position] = item
notifyItemChanged(position)
}
} }
override fun clearData() { override fun clearData() {
noteAdapter.clear() with(noteAdapter) {
items = mutableListOf()
notifyDataSetChanged()
}
} }
override fun showEmpty(show: Boolean) { override fun showEmpty(show: Boolean) {

View File

@ -1,77 +0,0 @@
package io.github.wulkanowy.ui.modules.note
import android.annotation.SuppressLint
import android.graphics.Typeface.BOLD
import android.graphics.Typeface.NORMAL
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import androidx.core.content.ContextCompat
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.Note
import io.github.wulkanowy.sdk.scrapper.notes.Note.CategoryType
import io.github.wulkanowy.utils.getThemeAttrColor
import io.github.wulkanowy.utils.toFormattedString
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_note.*
class NoteItem(val note: Note) : AbstractFlexibleItem<NoteItem.ViewHolder>() {
override fun getLayoutRes() = R.layout.item_note
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<*>>): ViewHolder {
return ViewHolder(view, adapter)
}
@SuppressLint("SetTextI18n")
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: ViewHolder, position: Int, payloads: MutableList<Any>?) {
holder.apply {
with(noteItemDate) {
text = note.date.toFormattedString()
setTypeface(null, if (note.isRead) NORMAL else BOLD)
}
with(noteItemType) {
text = note.category
setTypeface(null, if (note.isRead) NORMAL else BOLD)
}
with(noteItemPoints) {
text = "${if (note.points > 0) "+" else ""}${note.points}"
visibility = if (note.isPointsShow) VISIBLE else GONE
setTextColor(when(CategoryType.getByValue(note.categoryType)) {
CategoryType.POSITIVE -> ContextCompat.getColor(context, R.color.note_positive)
CategoryType.NEGATIVE -> ContextCompat.getColor(context, R.color.note_negative)
else -> context.getThemeAttrColor(android.R.attr.textColorPrimary)
})
}
noteItemTeacher.text = note.teacher
noteItemContent.text = note.content
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as NoteItem
if (note != other.note) return false
if (note.id != other.note.id) return false
return true
}
override fun hashCode(): Int {
var result = note.hashCode()
result = 31 * result + note.id.toInt()
return result
}
class ViewHolder(val view: View, adapter: FlexibleAdapter<*>) :
FlexibleViewHolder(view, adapter), LayoutContainer {
override val containerView: View
get() = contentView
}
}

View File

@ -1,6 +1,5 @@
package io.github.wulkanowy.ui.modules.note package io.github.wulkanowy.ui.modules.note
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.db.entities.Note
import io.github.wulkanowy.data.repositories.note.NoteRepository import io.github.wulkanowy.data.repositories.note.NoteRepository
import io.github.wulkanowy.data.repositories.semester.SemesterRepository import io.github.wulkanowy.data.repositories.semester.SemesterRepository
@ -53,8 +52,7 @@ class NotePresenter @Inject constructor(
disposable.add(studentRepository.getCurrentStudent() disposable.add(studentRepository.getCurrentStudent()
.flatMap { semesterRepository.getCurrentSemester(it).map { semester -> semester to it } } .flatMap { semesterRepository.getCurrentSemester(it).map { semester -> semester to it } }
.flatMap { noteRepository.getNotes(it.second, it.first, forceRefresh) } .flatMap { noteRepository.getNotes(it.second, it.first, forceRefresh) }
.map { items -> items.map { NoteItem(it) } } .map { items -> items.sortedByDescending { it.date } }
.map { items -> items.sortedByDescending { it.note.date } }
.subscribeOn(schedulers.backgroundThread) .subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread) .observeOn(schedulers.mainThread)
.doFinally { .doFinally {
@ -90,16 +88,14 @@ class NotePresenter @Inject constructor(
} }
} }
fun onNoteItemSelected(item: AbstractFlexibleItem<*>?) { fun onNoteItemSelected(note: Note, position: Int) {
if (item is NoteItem) { Timber.i("Select note item ${note.id}")
Timber.i("Select note item ${item.note.id}")
view?.run { view?.run {
showNoteDialog(item.note) showNoteDialog(note)
if (!item.note.isRead) { if (!note.isRead) {
item.note.isRead = true note.isRead = true
updateItem(item) updateItem(note, position)
updateNote(item.note) updateNote(note)
}
} }
} }
} }

View File

@ -1,6 +1,5 @@
package io.github.wulkanowy.ui.modules.note package io.github.wulkanowy.ui.modules.note
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import io.github.wulkanowy.data.db.entities.Note import io.github.wulkanowy.data.db.entities.Note
import io.github.wulkanowy.ui.base.BaseView import io.github.wulkanowy.ui.base.BaseView
@ -10,9 +9,9 @@ interface NoteView : BaseView {
fun initView() fun initView()
fun updateData(data: List<NoteItem>) fun updateData(data: List<Note>)
fun updateItem(item: AbstractFlexibleItem<*>) fun updateItem(item: Note, position: Int)
fun clearData() fun clearData()

View File

@ -0,0 +1,40 @@
package io.github.wulkanowy.ui.modules.schoolandteachers.teacher
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Teacher
import io.github.wulkanowy.databinding.ItemTeacherBinding
import javax.inject.Inject
class TeacherAdapter @Inject constructor() : RecyclerView.Adapter<TeacherAdapter.ItemViewHolder>() {
var items = emptyList<Teacher>()
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ItemViewHolder(
ItemTeacherBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val teacher = items[position]
with(holder.binding) {
teacherItemName.text = teacher.name
teacherItemSubject.text = if (teacher.subject.isNotBlank()) teacher.subject else root.context.getString(R.string.teacher_no_subject)
if (teacher.shortName.isNotBlank()) {
teacherItemShortName.visibility = View.VISIBLE
teacherItemShortName.text = "[${teacher.shortName}]"
} else {
teacherItemShortName.visibility = View.GONE
}
}
}
class ItemViewHolder(val binding: ItemTeacherBinding) : RecyclerView.ViewHolder(binding.root)
}

View File

@ -6,12 +6,11 @@ import android.view.View
import android.view.View.GONE import android.view.View.GONE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter import androidx.recyclerview.widget.LinearLayoutManager
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.R
import io.github.wulkanowy.data.db.entities.Teacher
import io.github.wulkanowy.ui.base.BaseFragment import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.widgets.DividerItemDecoration
import io.github.wulkanowy.ui.modules.main.MainView import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersChildView import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersChildView
import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment import io.github.wulkanowy.ui.modules.schoolandteachers.SchoolAndTeachersFragment
@ -25,7 +24,7 @@ class TeacherFragment : BaseFragment(), TeacherView, MainView.TitledView,
lateinit var presenter: TeacherPresenter lateinit var presenter: TeacherPresenter
@Inject @Inject
lateinit var teacherAdapter: FlexibleAdapter<AbstractFlexibleItem<*>> lateinit var teacherAdapter: TeacherAdapter
companion object { companion object {
fun newInstance() = TeacherFragment() fun newInstance() = TeacherFragment()
@ -37,7 +36,7 @@ class TeacherFragment : BaseFragment(), TeacherView, MainView.TitledView,
override val noSubjectString get() = getString(R.string.teacher_no_subject) override val noSubjectString get() = getString(R.string.teacher_no_subject)
override val isViewEmpty: Boolean override val isViewEmpty: Boolean
get() = teacherAdapter.isEmpty get() = teacherAdapter.items.isEmpty()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_teacher, container, false) return inflater.inflate(R.layout.fragment_teacher, container, false)
@ -50,28 +49,20 @@ class TeacherFragment : BaseFragment(), TeacherView, MainView.TitledView,
override fun initView() { override fun initView() {
teacherRecycler.run { teacherRecycler.run {
layoutManager = SmoothScrollLinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
adapter = teacherAdapter adapter = teacherAdapter
addItemDecoration(FlexibleItemDecoration(context) addItemDecoration(DividerItemDecoration(context))
.withDefaultDivider()
.withDrawDividerOnLastItem(false)
)
} }
teacherSwipe.setOnRefreshListener { presenter.onSwipeRefresh() } teacherSwipe.setOnRefreshListener { presenter.onSwipeRefresh() }
teacherErrorRetry.setOnClickListener { presenter.onRetry() } teacherErrorRetry.setOnClickListener { presenter.onRetry() }
teacherErrorDetails.setOnClickListener { presenter.onDetailsClick() } teacherErrorDetails.setOnClickListener { presenter.onDetailsClick() }
} }
override fun updateData(data: List<TeacherItem>) { override fun updateData(data: List<Teacher>) {
teacherAdapter.updateDataSet(data, true) with(teacherAdapter) {
items = data
notifyDataSetChanged()
} }
override fun updateItem(item: AbstractFlexibleItem<*>) {
teacherAdapter.updateItem(item)
}
override fun clearData() {
teacherAdapter.clear()
} }
override fun showEmpty(show: Boolean) { override fun showEmpty(show: Boolean) {

Some files were not shown because too many files have changed in this diff Show More