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: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
kapt {
correctErrorTypes = true
}
android { android {
compileSdkVersion 28 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 { dependencies {
def epoxy_version = "2.19.0"
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(":matrix-sdk-android") implementation project(":matrix-sdk-android")
@ -30,6 +48,9 @@ dependencies {
implementation 'com.jakewharton.timber:timber:4.7.1' 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:$koin_version"
implementation "org.koin:koin-android-scope:$koin_version" implementation "org.koin:koin-android-scope:$koin_version"
implementation "org.koin:koin-android-viewmodel:$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:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.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 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 package im.vector.riotredesign.features.home
import android.arch.lifecycle.Observer
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle 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.R
import im.vector.riotredesign.core.platform.RiotActivity import im.vector.riotredesign.core.platform.RiotActivity
import org.koin.android.ext.android.inject
import timber.log.Timber
class HomeActivity : RiotActivity() { class HomeActivity : RiotActivity() {
private val matrix by inject<Matrix>()
private val currentSession = matrix.currentSession!!
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home) setContentView(R.layout.activity_home)
currentSession.rooms().observe(this, Observer<List<Room>> { roomList ->
if (roomList == null) { if (savedInstanceState == null) {
return@Observer val roomListFragment = RoomListFragment.newInstance()
val ft = supportFragmentManager.beginTransaction()
ft.replace(R.id.homeFragmentContainer, roomListFragment)
ft.commit()
} }
Timber.v("Observe rooms: %d", roomList.size)
})
} }
companion object { 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"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
tools:context=".features.login.LoginActivity"> tools:context=".features.login.LoginActivity">
<Button <FrameLayout
android:id="@+id/startSyncButton" android:id="@+id/homeFragmentContainer"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="match_parent" />
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" />
</android.support.constraint.ConstraintLayout> </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" />