mirror of
https://github.com/element-hq/element-android
synced 2024-11-26 03:15:40 +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")
|
||||
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-scope:$koin_version"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package im.vector.riotredesign.core.helpers
|
||||
package im.vector.riotredesign.core.epoxy
|
||||
|
||||
import android.view.View
|
||||
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.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.os.Bundle
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.extensions.replaceFragment
|
||||
import im.vector.riotredesign.core.platform.RiotActivity
|
||||
|
||||
|
||||
|
@ -15,9 +16,7 @@ class HomeActivity : RiotActivity() {
|
|||
|
||||
if (savedInstanceState == null) {
|
||||
val roomListFragment = RoomListFragment.newInstance()
|
||||
val ft = supportFragmentManager.beginTransaction()
|
||||
ft.replace(R.id.homeFragmentContainer, roomListFragment)
|
||||
ft.commit()
|
||||
replaceFragment(roomListFragment, R.id.homeFragmentContainer)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.helpers.KotlinModel
|
||||
import im.vector.riotredesign.core.epoxy.KotlinModel
|
||||
|
||||
data class RoomItem(
|
||||
val title: String,
|
||||
|
|
|
@ -5,10 +5,10 @@ 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.room.Room
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.extensions.replaceFragment
|
||||
import im.vector.riotredesign.core.platform.RiotFragment
|
||||
import kotlinx.android.synthetic.main.fragment_room_list.*
|
||||
import org.koin.android.ext.android.inject
|
||||
|
@ -42,7 +42,8 @@ class RoomListFragment : RiotFragment(), RoomController.Callback {
|
|||
}
|
||||
|
||||
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"
|
||||
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>
|
|
@ -1,6 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/titleView"
|
||||
android:layout_width="match_parent"
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
.equalTo("chunk.prevToken", chunk?.prevToken)
|
||||
.equalTo("chunk.prevToken", chunk.prevToken)
|
||||
.and()
|
||||
.equalTo("chunk.nextToken", chunk?.nextToken)
|
||||
.equalTo("chunk.nextToken", chunk.nextToken)
|
||||
}
|
||||
|
||||
fun RealmResults<EventEntity>.getLast(type: String? = null): EventEntity? {
|
||||
|
|
|
@ -27,7 +27,11 @@ data class DefaultRoom(
|
|||
override fun liveTimeline(): LiveData<PagedList<Event>> {
|
||||
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
|
||||
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 livePagedListBuilder = LivePagedListBuilder(domainSourceFactory, 20).setBoundaryCallback(boundaryCallback)
|
||||
|
|
Loading…
Reference in a new issue