diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser index 593e72081d..70dd0b4177 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 145162cb5f..5b1073c05c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" diff --git a/app/src/main/java/im/vector/riotredesign/core/helpers/KotlinEpoxyHolder.kt b/app/src/main/java/im/vector/riotredesign/core/epoxy/KotlinEpoxyHolder.kt similarity index 96% rename from app/src/main/java/im/vector/riotredesign/core/helpers/KotlinEpoxyHolder.kt rename to app/src/main/java/im/vector/riotredesign/core/epoxy/KotlinEpoxyHolder.kt index 9020b8fe7a..60c313afba 100644 --- a/app/src/main/java/im/vector/riotredesign/core/helpers/KotlinEpoxyHolder.kt +++ b/app/src/main/java/im/vector/riotredesign/core/epoxy/KotlinEpoxyHolder.kt @@ -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 diff --git a/app/src/main/java/im/vector/riotredesign/core/helpers/KotlinModel.kt b/app/src/main/java/im/vector/riotredesign/core/epoxy/KotlinModel.kt similarity index 96% rename from app/src/main/java/im/vector/riotredesign/core/helpers/KotlinModel.kt rename to app/src/main/java/im/vector/riotredesign/core/epoxy/KotlinModel.kt index e122489839..b69014ee90 100644 --- a/app/src/main/java/im/vector/riotredesign/core/helpers/KotlinModel.kt +++ b/app/src/main/java/im/vector/riotredesign/core/epoxy/KotlinModel.kt @@ -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 diff --git a/app/src/main/java/im/vector/riotredesign/core/extensions/Activity.kt b/app/src/main/java/im/vector/riotredesign/core/extensions/Activity.kt new file mode 100644 index 0000000000..96578cd101 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/core/extensions/Activity.kt @@ -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) } +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/core/extensions/Fragment.kt b/app/src/main/java/im/vector/riotredesign/core/extensions/Fragment.kt new file mode 100644 index 0000000000..8400192926 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/core/extensions/Fragment.kt @@ -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) } +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/core/extensions/FragmentManager.kt b/app/src/main/java/im/vector/riotredesign/core/extensions/FragmentManager.kt new file mode 100644 index 0000000000..6fcc045e60 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/core/extensions/FragmentManager.kt @@ -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() +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/core/utils/FragmentArgumentDelegate.kt b/app/src/main/java/im/vector/riotredesign/core/utils/FragmentArgumentDelegate.kt new file mode 100644 index 0000000000..28a5419817 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/core/utils/FragmentArgumentDelegate.kt @@ -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 : kotlin.properties.ReadWriteProperty { + + 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") + } + } +} \ 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 3e7ec7e05d..05598e8efe 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 @@ -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) } } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/LoadingItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/LoadingItem.kt new file mode 100644 index 0000000000..2862b35cfb --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/LoadingItem.kt @@ -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) \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/RoomDetailFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/RoomDetailFragment.kt new file mode 100644 index 0000000000..2bfc08216e --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/RoomDetailFragment.kt @@ -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() + private val currentSession = matrix.currentSession!! + private var roomId by FragmentArgumentDelegate() + + 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?) { + timelineController.submitList(events) + } + + 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/java/im/vector/riotredesign/features/home/RoomItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/RoomItem.kt index fa4d998afa..c697c5af88 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/RoomItem.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/RoomItem.kt @@ -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, 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 index 3df0f78521..f1b891734a 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/RoomListFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/RoomListFragment.kt @@ -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) } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/TimelineEventController.kt b/app/src/main/java/im/vector/riotredesign/features/home/TimelineEventController.kt new file mode 100644 index 0000000000..f7519ffc09 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/TimelineEventController.kt @@ -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( + 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 + } + +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/TimelineEventItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/TimelineEventItem.kt new file mode 100644 index 0000000000..719a8c5dc3 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/TimelineEventItem.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.epoxy.KotlinModel + +data class TimelineEventItem( + val title: String, + val listener: (() -> Unit)? = null +) : KotlinModel(R.layout.item_event) { + + 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/res/layout/fragment_room_detail.xml b/app/src/main/res/layout/fragment_room_detail.xml index d8fb3247f0..a8c7d0e03a 100644 --- a/app/src/main/res/layout/fragment_room_detail.xml +++ b/app/src/main/res/layout/fragment_room_detail.xml @@ -3,4 +3,9 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_event.xml b/app/src/main/res/layout/item_event.xml index d8fb3247f0..4ab22892a8 100644 --- a/app/src/main/res/layout/item_event.xml +++ b/app/src/main/res/layout/item_event.xml @@ -1,6 +1,10 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/item_loading.xml b/app/src/main/res/layout/item_loading.xml new file mode 100644 index 0000000000..62543a335f --- /dev/null +++ b/app/src/main/res/layout/item_loading.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt index 0ffc663398..0182d4c401 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/EventEntityQueries.kt @@ -11,11 +11,11 @@ fun EventEntity.Companion.where(realm: Realm, roomId: String): RealmQuery { +fun EventEntity.Companion.where(realm: Realm, chunk: ChunkEntity): RealmQuery { 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.getLast(type: String? = null): EventEntity? { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt index a9a73c0bbc..d524837194 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt @@ -27,7 +27,11 @@ data class DefaultRoom( override fun liveTimeline(): LiveData> { 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)