mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-22 01:15:54 +03:00
Use epoxy to render pagedList : WIP
This commit is contained in:
parent
06a5253fd9
commit
ecbc64082f
20 changed files with 239 additions and 17 deletions
Binary file not shown.
|
@ -50,6 +50,8 @@ dependencies {
|
||||||
|
|
||||||
implementation("com.airbnb.android:epoxy:$epoxy_version")
|
implementation("com.airbnb.android:epoxy:$epoxy_version")
|
||||||
kapt "com.airbnb.android:epoxy-processor:$epoxy_version"
|
kapt "com.airbnb.android:epoxy-processor:$epoxy_version"
|
||||||
|
implementation "com.airbnb.android:epoxy-paging:$epoxy_version"
|
||||||
|
|
||||||
|
|
||||||
implementation "org.koin:koin-android:$koin_version"
|
implementation "org.koin:koin-android:$koin_version"
|
||||||
implementation "org.koin:koin-android-scope:$koin_version"
|
implementation "org.koin:koin-android-scope:$koin_version"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package im.vector.riotredesign.core.helpers
|
package im.vector.riotredesign.core.epoxy
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.airbnb.epoxy.EpoxyHolder
|
import com.airbnb.epoxy.EpoxyHolder
|
|
@ -1,4 +1,4 @@
|
||||||
package im.vector.riotredesign.core.helpers
|
package im.vector.riotredesign.core.epoxy
|
||||||
|
|
||||||
import android.support.annotation.IdRes
|
import android.support.annotation.IdRes
|
||||||
import android.support.annotation.LayoutRes
|
import android.support.annotation.LayoutRes
|
|
@ -0,0 +1,12 @@
|
||||||
|
package im.vector.riotredesign.core.extensions
|
||||||
|
|
||||||
|
import android.support.v4.app.Fragment
|
||||||
|
import android.support.v7.app.AppCompatActivity
|
||||||
|
|
||||||
|
fun AppCompatActivity.addFragment(fragment: Fragment, frameId: Int) {
|
||||||
|
supportFragmentManager.inTransaction { add(frameId, fragment) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun AppCompatActivity.replaceFragment(fragment: Fragment, frameId: Int) {
|
||||||
|
supportFragmentManager.inTransaction { replace(frameId, fragment) }
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package im.vector.riotredesign.core.extensions
|
||||||
|
|
||||||
|
import android.support.v4.app.Fragment
|
||||||
|
|
||||||
|
fun Fragment.addFragment(fragment: Fragment, frameId: Int) {
|
||||||
|
fragmentManager?.inTransaction { add(frameId, fragment) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Fragment.replaceFragment(fragment: Fragment, frameId: Int) {
|
||||||
|
fragmentManager?.inTransaction { replace(frameId, fragment) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Fragment.addChildFragment(fragment: Fragment, frameId: Int) {
|
||||||
|
childFragmentManager.inTransaction { add(frameId, fragment) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Fragment.replaceChildFragment(fragment: Fragment, frameId: Int) {
|
||||||
|
childFragmentManager.inTransaction { replace(frameId, fragment) }
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package im.vector.riotredesign.core.extensions
|
||||||
|
|
||||||
|
import android.support.v4.app.FragmentManager
|
||||||
|
import android.support.v4.app.FragmentTransaction
|
||||||
|
|
||||||
|
inline fun FragmentManager.inTransaction(func: FragmentTransaction.() -> FragmentTransaction) {
|
||||||
|
beginTransaction().func().commit()
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package im.vector.riotredesign.core.utils
|
||||||
|
|
||||||
|
import android.os.Binder
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v4.app.BundleCompat
|
||||||
|
import android.support.v4.app.Fragment
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
class FragmentArgumentDelegate<T : Any> : kotlin.properties.ReadWriteProperty<Fragment, T> {
|
||||||
|
|
||||||
|
var value: T? = null
|
||||||
|
|
||||||
|
override operator fun getValue(thisRef: android.support.v4.app.Fragment, property: kotlin.reflect.KProperty<*>): T {
|
||||||
|
if (value == null) {
|
||||||
|
val args = thisRef.arguments
|
||||||
|
?: throw IllegalStateException("Cannot read property ${property.name} if no arguments have been set")
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
value = args.get(property.name) as T
|
||||||
|
}
|
||||||
|
return value ?: throw IllegalStateException("Property ${property.name} could not be read")
|
||||||
|
}
|
||||||
|
|
||||||
|
override operator fun setValue(thisRef: Fragment, property: KProperty<*>, value: T) {
|
||||||
|
if (thisRef.arguments == null) {
|
||||||
|
thisRef.arguments = Bundle()
|
||||||
|
}
|
||||||
|
val args = thisRef.arguments!!
|
||||||
|
val key = property.name
|
||||||
|
|
||||||
|
when (value) {
|
||||||
|
is String -> args.putString(key, value)
|
||||||
|
is Int -> args.putInt(key, value)
|
||||||
|
is Short -> args.putShort(key, value)
|
||||||
|
is Long -> args.putLong(key, value)
|
||||||
|
is Byte -> args.putByte(key, value)
|
||||||
|
is ByteArray -> args.putByteArray(key, value)
|
||||||
|
is Char -> args.putChar(key, value)
|
||||||
|
is CharArray -> args.putCharArray(key, value)
|
||||||
|
is CharSequence -> args.putCharSequence(key, value)
|
||||||
|
is Float -> args.putFloat(key, value)
|
||||||
|
is Bundle -> args.putBundle(key, value)
|
||||||
|
is Binder -> BundleCompat.putBinder(args, key, value)
|
||||||
|
is android.os.Parcelable -> args.putParcelable(key, value)
|
||||||
|
is java.io.Serializable -> args.putSerializable(key, value)
|
||||||
|
else -> throw IllegalStateException("Type ${value.javaClass.canonicalName} of property ${property.name} is not supported")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
|
import im.vector.riotredesign.core.extensions.replaceFragment
|
||||||
import im.vector.riotredesign.core.platform.RiotActivity
|
import im.vector.riotredesign.core.platform.RiotActivity
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,9 +16,7 @@ class HomeActivity : RiotActivity() {
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
val roomListFragment = RoomListFragment.newInstance()
|
val roomListFragment = RoomListFragment.newInstance()
|
||||||
val ft = supportFragmentManager.beginTransaction()
|
replaceFragment(roomListFragment, R.id.homeFragmentContainer)
|
||||||
ft.replace(R.id.homeFragmentContainer, roomListFragment)
|
|
||||||
ft.commit()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package im.vector.riotredesign.features.home
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.widget.ProgressBar
|
||||||
|
import com.airbnb.epoxy.ModelView
|
||||||
|
|
||||||
|
@ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT)
|
||||||
|
class LoadingItem(context: Context) : ProgressBar(context)
|
|
@ -0,0 +1,60 @@
|
||||||
|
package im.vector.riotredesign.features.home
|
||||||
|
|
||||||
|
import android.arch.lifecycle.Observer
|
||||||
|
import android.arch.paging.PagedList
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Toast
|
||||||
|
import im.vector.matrix.android.api.Matrix
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.matrix.android.api.session.room.Room
|
||||||
|
import im.vector.riotredesign.R
|
||||||
|
import im.vector.riotredesign.core.platform.RiotFragment
|
||||||
|
import im.vector.riotredesign.core.utils.FragmentArgumentDelegate
|
||||||
|
import org.koin.android.ext.android.inject
|
||||||
|
|
||||||
|
class RoomDetailFragment : RiotFragment(), RoomController.Callback {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun newInstance(roomId: String): RoomDetailFragment {
|
||||||
|
return RoomDetailFragment().apply {
|
||||||
|
this.roomId = roomId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val matrix by inject<Matrix>()
|
||||||
|
private val currentSession = matrix.currentSession!!
|
||||||
|
private var roomId by FragmentArgumentDelegate<String>()
|
||||||
|
|
||||||
|
private val timelineController = TimelineEventController()
|
||||||
|
private val room: Room? by lazy {
|
||||||
|
currentSession.getRoom(roomId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
return inflater.inflate(R.layout.fragment_room_detail, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
|
super.onActivityCreated(savedInstanceState)
|
||||||
|
if (room == null) {
|
||||||
|
activity?.onBackPressed()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
room?.liveTimeline()?.observe(this, Observer { renderEvents(it) })
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderEvents(events: PagedList<Event>?) {
|
||||||
|
timelineController.submitList(events)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRoomSelected(room: Room) {
|
||||||
|
Toast.makeText(context, "Room ${room.roomId} clicked", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ package im.vector.riotredesign.features.home
|
||||||
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
import im.vector.riotredesign.core.helpers.KotlinModel
|
import im.vector.riotredesign.core.epoxy.KotlinModel
|
||||||
|
|
||||||
data class RoomItem(
|
data class RoomItem(
|
||||||
val title: String,
|
val title: String,
|
||||||
|
|
|
@ -5,10 +5,10 @@ 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 android.widget.Toast
|
|
||||||
import im.vector.matrix.android.api.Matrix
|
import im.vector.matrix.android.api.Matrix
|
||||||
import im.vector.matrix.android.api.session.room.Room
|
import im.vector.matrix.android.api.session.room.Room
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
|
import im.vector.riotredesign.core.extensions.replaceFragment
|
||||||
import im.vector.riotredesign.core.platform.RiotFragment
|
import im.vector.riotredesign.core.platform.RiotFragment
|
||||||
import kotlinx.android.synthetic.main.fragment_room_list.*
|
import kotlinx.android.synthetic.main.fragment_room_list.*
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
|
@ -42,7 +42,8 @@ class RoomListFragment : RiotFragment(), RoomController.Callback {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRoomSelected(room: Room) {
|
override fun onRoomSelected(room: Room) {
|
||||||
Toast.makeText(context, "Room ${room.roomId} clicked", Toast.LENGTH_SHORT).show()
|
val detailFragment = RoomDetailFragment.newInstance(room.roomId)
|
||||||
|
replaceFragment(detailFragment, R.id.homeFragmentContainer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
package im.vector.riotredesign.features.home
|
||||||
|
|
||||||
|
import com.airbnb.epoxy.EpoxyAsyncUtil
|
||||||
|
import com.airbnb.epoxy.EpoxyModel
|
||||||
|
import com.airbnb.epoxy.paging.PagedListEpoxyController
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
|
||||||
|
class TimelineEventController : PagedListEpoxyController<Event>(
|
||||||
|
modelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
|
||||||
|
) {
|
||||||
|
|
||||||
|
override fun buildItemModel(currentPosition: Int, item: Event?): EpoxyModel<*> {
|
||||||
|
return if (item == null) {
|
||||||
|
LoadingItemModel_().id(-currentPosition)
|
||||||
|
} else {
|
||||||
|
TimelineEventItem(item.eventId ?: "$currentPosition").id(currentPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
isDebugLoggingEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onExceptionSwallowed(exception: RuntimeException) {
|
||||||
|
throw exception
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package im.vector.riotredesign.features.home
|
||||||
|
|
||||||
|
import android.widget.TextView
|
||||||
|
import im.vector.riotredesign.R
|
||||||
|
import im.vector.riotredesign.core.epoxy.KotlinModel
|
||||||
|
|
||||||
|
data class TimelineEventItem(
|
||||||
|
val title: String,
|
||||||
|
val listener: (() -> Unit)? = null
|
||||||
|
) : KotlinModel(R.layout.item_event) {
|
||||||
|
|
||||||
|
val titleView by bind<TextView>(R.id.titleView)
|
||||||
|
|
||||||
|
override fun bind() {
|
||||||
|
titleView.setOnClickListener { listener?.invoke() }
|
||||||
|
titleView.text = title
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,4 +3,9 @@
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
|
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||||
|
android:id="@+id/epoxyRecyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
</android.support.constraint.ConstraintLayout>
|
|
@ -1,6 +1,10 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<android.support.constraint.ConstraintLayout
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_height="match_parent">
|
android:id="@+id/titleView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
</android.support.constraint.ConstraintLayout>
|
android:layout_height="80dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:text="Room name" />
|
6
app/src/main/res/layout/item_loading.xml
Normal file
6
app/src/main/res/layout/item_loading.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="80dp"
|
||||||
|
android:padding="16dp" />
|
|
@ -11,11 +11,11 @@ fun EventEntity.Companion.where(realm: Realm, roomId: String): RealmQuery<EventE
|
||||||
.equalTo("chunk.room.roomId", roomId)
|
.equalTo("chunk.room.roomId", roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun EventEntity.Companion.where(realm: Realm, chunk: ChunkEntity?): RealmQuery<EventEntity> {
|
fun EventEntity.Companion.where(realm: Realm, chunk: ChunkEntity): RealmQuery<EventEntity> {
|
||||||
return realm.where(EventEntity::class.java)
|
return realm.where(EventEntity::class.java)
|
||||||
.equalTo("chunk.prevToken", chunk?.prevToken)
|
.equalTo("chunk.prevToken", chunk.prevToken)
|
||||||
.and()
|
.and()
|
||||||
.equalTo("chunk.nextToken", chunk?.nextToken)
|
.equalTo("chunk.nextToken", chunk.nextToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun RealmResults<EventEntity>.getLast(type: String? = null): EventEntity? {
|
fun RealmResults<EventEntity>.getLast(type: String? = null): EventEntity? {
|
||||||
|
|
|
@ -27,7 +27,11 @@ data class DefaultRoom(
|
||||||
override fun liveTimeline(): LiveData<PagedList<Event>> {
|
override fun liveTimeline(): LiveData<PagedList<Event>> {
|
||||||
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
|
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
|
||||||
val lastChunk = ChunkEntity.where(realm, roomId).findAll().last()
|
val lastChunk = ChunkEntity.where(realm, roomId).findAll().last()
|
||||||
EventEntity.where(realm, lastChunk)
|
if (lastChunk == null) {
|
||||||
|
EventEntity.where(realm, roomId)
|
||||||
|
} else {
|
||||||
|
EventEntity.where(realm, lastChunk)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val domainSourceFactory = realmDataSourceFactory.map { EventMapper.map(it) }
|
val domainSourceFactory = realmDataSourceFactory.map { EventMapper.map(it) }
|
||||||
val livePagedListBuilder = LivePagedListBuilder(domainSourceFactory, 20).setBoundaryCallback(boundaryCallback)
|
val livePagedListBuilder = LivePagedListBuilder(domainSourceFactory, 20).setBoundaryCallback(boundaryCallback)
|
||||||
|
|
Loading…
Reference in a new issue