diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser index 4569da29e2..593e72081d 100644 Binary files a/.idea/caches/build_file_checksums.ser and b/.idea/caches/build_file_checksums.ser differ diff --git a/app/build.gradle b/app/build.gradle index 9712821c65..145162cb5f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' } + + diff --git a/app/src/main/java/im/vector/riotredesign/core/helpers/KotlinEpoxyHolder.kt b/app/src/main/java/im/vector/riotredesign/core/helpers/KotlinEpoxyHolder.kt new file mode 100644 index 0000000000..9020b8fe7a --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/core/helpers/KotlinEpoxyHolder.kt @@ -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 bind(id: Int): ReadOnlyProperty = + 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( + private val initializer: (KotlinEpoxyHolder, KProperty<*>) -> V + ) : ReadOnlyProperty { + 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 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/core/helpers/KotlinModel.kt b/app/src/main/java/im/vector/riotredesign/core/helpers/KotlinModel.kt new file mode 100644 index 0000000000..e122489839 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/core/helpers/KotlinModel.kt @@ -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() { + + 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 bind(@IdRes id: Int) = object : ReadOnlyProperty { + 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.") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/core/platform/RiotFragment.kt b/app/src/main/java/im/vector/riotredesign/core/platform/RiotFragment.kt index 3a6a5f6caa..4006055b7c 100644 --- a/app/src/main/java/im/vector/riotredesign/core/platform/RiotFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/core/platform/RiotFragment.kt @@ -2,5 +2,5 @@ package im.vector.riotredesign.core.platform import android.support.v4.app.Fragment -class RiotFragment : Fragment() { +open class RiotFragment : Fragment() { } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt index 4b5413f493..3e7ec7e05d 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt @@ -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() - private val currentSession = matrix.currentSession!! - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_home) - currentSession.rooms().observe(this, Observer> { 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 { diff --git a/app/src/main/java/im/vector/riotredesign/features/home/RoomController.kt b/app/src/main/java/im/vector/riotredesign/features/home/RoomController.kt new file mode 100644 index 0000000000..00a3035bf1 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/RoomController.kt @@ -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>() { + + override fun buildModels(data: List?) { + data?.forEach { + RoomItem(it.roomId, listener = { callback?.onRoomSelected(it) }) + .id(it.roomId) + .addTo(this) + } + } + + interface Callback { + fun onRoomSelected(room: Room) + } + +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/RoomItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/RoomItem.kt new file mode 100644 index 0000000000..fa4d998afa --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/RoomItem.kt @@ -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(R.id.titleView) + + override fun bind() { + titleView.setOnClickListener { listener?.invoke() } + titleView.text = title + } +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/RoomListFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/RoomListFragment.kt new file mode 100644 index 0000000000..3df0f78521 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/RoomListFragment.kt @@ -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() + 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> { renderRooms(it) }) + } + + private fun renderRooms(rooms: List?) { + roomController.setData(rooms) + } + + override fun onRoomSelected(room: Room) { + Toast.makeText(context, "Room ${room.roomId} clicked", Toast.LENGTH_SHORT).show() + } + + +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml index e5007dce7d..1238982278 100644 --- a/app/src/main/res/layout/activity_home.xml +++ b/app/src/main/res/layout/activity_home.xml @@ -1,40 +1,15 @@ -