Introduce epoxy in demo app

This commit is contained in:
ganfra 2018-10-19 14:26:38 +02:00
parent e323c858ab
commit 06a5253fd9
14 changed files with 239 additions and 44 deletions

View File

@ -1,6 +1,11 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
kapt {
correctErrorTypes = true
}
android {
compileSdkVersion 28
@ -20,7 +25,20 @@ android {
}
}
configurations.all { strategy ->
strategy.resolutionStrategy.eachDependency { details ->
if (details.requested.group == 'com.android.support') {
details.useVersion "28.0.0"
}
}
}
dependencies {
def epoxy_version = "2.19.0"
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(":matrix-sdk-android")
@ -30,6 +48,9 @@ dependencies {
implementation 'com.jakewharton.timber:timber:4.7.1'
implementation("com.airbnb.android:epoxy:$epoxy_version")
kapt "com.airbnb.android:epoxy-processor:$epoxy_version"
implementation "org.koin:koin-android:$koin_version"
implementation "org.koin:koin-android-scope:$koin_version"
implementation "org.koin:koin-android-viewmodel:$koin_version"
@ -38,3 +59,5 @@ dependencies {
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

View File

@ -0,0 +1,45 @@
package im.vector.riotredesign.core.helpers
import android.view.View
import com.airbnb.epoxy.EpoxyHolder
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
/**
* A pattern for easier view binding with an [EpoxyHolder]
*
* See [SampleKotlinModelWithHolder] for a usage example.
*/
abstract class KotlinEpoxyHolder : EpoxyHolder() {
private lateinit var view: View
override fun bindView(itemView: View) {
view = itemView
}
protected fun <V : View> bind(id: Int): ReadOnlyProperty<KotlinEpoxyHolder, V> =
Lazy { holder: KotlinEpoxyHolder, prop ->
holder.view.findViewById(id) as V?
?: throw IllegalStateException("View ID $id for '${prop.name}' not found.")
}
/**
* Taken from Kotterknife.
* https://github.com/JakeWharton/kotterknife
*/
private class Lazy<V>(
private val initializer: (KotlinEpoxyHolder, KProperty<*>) -> V
) : ReadOnlyProperty<KotlinEpoxyHolder, V> {
private object EMPTY
private var value: Any? = EMPTY
override fun getValue(thisRef: KotlinEpoxyHolder, property: KProperty<*>): V {
if (value == EMPTY) {
value = initializer(thisRef, property)
}
@Suppress("UNCHECKED_CAST")
return value as V
}
}
}

View File

@ -0,0 +1,39 @@
package im.vector.riotredesign.core.helpers
import android.support.annotation.IdRes
import android.support.annotation.LayoutRes
import android.view.View
import com.airbnb.epoxy.EpoxyModel
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
abstract class KotlinModel(
@LayoutRes private val layoutRes: Int
) : EpoxyModel<View>() {
private var view: View? = null
abstract fun bind()
override fun bind(view: View) {
this.view = view
bind()
}
override fun unbind(view: View) {
this.view = null
}
override fun getDefaultLayout() = layoutRes
protected fun <V : View> bind(@IdRes id: Int) = object : ReadOnlyProperty<KotlinModel, V> {
override fun getValue(thisRef: KotlinModel, property: KProperty<*>): V {
// This is not efficient because it looks up the view by id every time (it loses
// the pattern of a "holder" to cache that look up). But it is simple to use and could
// be optimized with a map
@Suppress("UNCHECKED_CAST")
return view?.findViewById(id) as V?
?: throw IllegalStateException("View ID $id for '${property.name}' not found.")
}
}
}

View File

@ -2,5 +2,5 @@ package im.vector.riotredesign.core.platform
import android.support.v4.app.Fragment
class RiotFragment : Fragment() {
open class RiotFragment : Fragment() {
}

View File

@ -1,31 +1,24 @@
package im.vector.riotredesign.features.home
import android.arch.lifecycle.Observer
import android.content.Context
import android.content.Intent
import android.os.Bundle
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.platform.RiotActivity
import org.koin.android.ext.android.inject
import timber.log.Timber
class HomeActivity : RiotActivity() {
private val matrix by inject<Matrix>()
private val currentSession = matrix.currentSession!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
currentSession.rooms().observe(this, Observer<List<Room>> { roomList ->
if (roomList == null) {
return@Observer
}
Timber.v("Observe rooms: %d", roomList.size)
})
if (savedInstanceState == null) {
val roomListFragment = RoomListFragment.newInstance()
val ft = supportFragmentManager.beginTransaction()
ft.replace(R.id.homeFragmentContainer, roomListFragment)
ft.commit()
}
}
companion object {

View File

@ -0,0 +1,20 @@
package im.vector.riotredesign.features.home
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.matrix.android.api.session.room.Room
class RoomController(private val callback: Callback? = null) : TypedEpoxyController<List<Room>>() {
override fun buildModels(data: List<Room>?) {
data?.forEach {
RoomItem(it.roomId, listener = { callback?.onRoomSelected(it) })
.id(it.roomId)
.addTo(this)
}
}
interface Callback {
fun onRoomSelected(room: Room)
}
}

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.helpers.KotlinModel
data class RoomItem(
val title: String,
val listener: (() -> Unit)? = null
) : KotlinModel(R.layout.item_room) {
val titleView by bind<TextView>(R.id.titleView)
override fun bind() {
titleView.setOnClickListener { listener?.invoke() }
titleView.text = title
}
}

View File

@ -0,0 +1,49 @@
package im.vector.riotredesign.features.home
import android.arch.lifecycle.Observer
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.platform.RiotFragment
import kotlinx.android.synthetic.main.fragment_room_list.*
import org.koin.android.ext.android.inject
class RoomListFragment : RiotFragment(), RoomController.Callback {
companion object {
fun newInstance(): RoomListFragment {
return RoomListFragment()
}
}
private val matrix by inject<Matrix>()
private val currentSession = matrix.currentSession!!
private val roomController = RoomController(this)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_room_list, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
epoxyRecyclerView.setController(roomController)
currentSession.rooms().observe(this, Observer<List<Room>> { renderRooms(it) })
}
private fun renderRooms(rooms: List<Room>?) {
roomController.setData(rooms)
}
override fun onRoomSelected(room: Room) {
Toast.makeText(context, "Room ${room.roomId} clicked", Toast.LENGTH_SHORT).show()
}
}

View File

@ -1,40 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".features.login.LoginActivity">
<Button
android:id="@+id/startSyncButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="Start sync"
app:layout_constraintBottom_toTopOf="@+id/stopSyncButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/stopSyncButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="stop sync"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/startSyncButton" />
<FrameLayout
android:id="@+id/homeFragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.constraint.ConstraintLayout>

View File

@ -0,0 +1,6 @@
<?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>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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" />
</FrameLayout>

View File

@ -0,0 +1,6 @@
<?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>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<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" />