Use epoxy to render pagedList : WIP

This commit is contained in:
ganfra 2018-10-19 15:30:40 +02:00
parent 06a5253fd9
commit ecbc64082f
20 changed files with 239 additions and 17 deletions

View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -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) }
}

View file

@ -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) }
}

View file

@ -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()
}

View file

@ -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")
}
}
}

View file

@ -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()
} }
} }

View file

@ -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)

View file

@ -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()
}
}

View file

@ -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,

View file

@ -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)
} }

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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>

View file

@ -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" />

View 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" />

View file

@ -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? {

View file

@ -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)