Merge branch 'feature/epoxy' into develop

This commit is contained in:
ganfra 2019-02-19 17:49:36 +01:00
commit abb44839af
32 changed files with 349 additions and 321 deletions

View File

@ -76,6 +76,7 @@ dependencies {
implementation 'com.jakewharton.threetenabp:threetenabp:1.1.1' implementation 'com.jakewharton.threetenabp:threetenabp:1.1.1'
implementation 'com.jakewharton.timber:timber:4.7.1' implementation 'com.jakewharton.timber:timber:4.7.1'
implementation 'com.facebook.stetho:stetho:1.5.0'
// rx // rx
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0' implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'

View File

@ -19,6 +19,7 @@ package im.vector.riotredesign
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import androidx.multidex.MultiDex import androidx.multidex.MultiDex
import com.facebook.stetho.Stetho
import com.jakewharton.threetenabp.AndroidThreeTen import com.jakewharton.threetenabp.AndroidThreeTen
import im.vector.matrix.android.BuildConfig import im.vector.matrix.android.BuildConfig
import im.vector.riotredesign.core.di.AppModule import im.vector.riotredesign.core.di.AppModule
@ -33,6 +34,7 @@ class Riot : Application() {
super.onCreate() super.onCreate()
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree()) Timber.plant(Timber.DebugTree())
Stetho.initializeWithDefaults(this)
} }
AndroidThreeTen.init(this) AndroidThreeTen.init(this)
startKoin(listOf(AppModule(this).definition), logger = EmptyLogger()) startKoin(listOf(AppModule(this).definition), logger = EmptyLogger())

View File

@ -1,74 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotredesign.core.epoxy
import android.view.View
import androidx.annotation.IdRes
import androidx.annotation.LayoutRes
import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.OnModelVisibilityStateChangedListener
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
abstract class KotlinModel(
@LayoutRes private val layoutRes: Int
) : EpoxyModel<View>() {
private var view: View? = null
private var onBindCallback: (() -> Unit)? = null
private var onModelVisibilityStateChangedListener: OnModelVisibilityStateChangedListener<KotlinModel, View>? = null
abstract fun bind()
override fun bind(view: View) {
this.view = view
onBindCallback?.invoke()
bind()
}
override fun unbind(view: View) {
this.view = null
}
fun onBind(lambda: (() -> Unit)?): KotlinModel {
onBindCallback = lambda
return this
}
override fun onVisibilityStateChanged(visibilityState: Int, view: View) {
onModelVisibilityStateChangedListener?.onVisibilityStateChanged(this, view, visibilityState)
super.onVisibilityStateChanged(visibilityState, view)
}
fun setOnVisibilityStateChanged(listener: OnModelVisibilityStateChangedListener<KotlinModel, View>): KotlinModel {
this.onModelVisibilityStateChangedListener = listener
return this
}
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

@ -26,15 +26,15 @@ import kotlin.reflect.KProperty
* *
* See [SampleKotlinModelWithHolder] for a usage example. * See [SampleKotlinModelWithHolder] for a usage example.
*/ */
abstract class KotlinEpoxyHolder : EpoxyHolder() { abstract class RiotEpoxyHolder : EpoxyHolder() {
private lateinit var view: View private lateinit var view: View
override fun bindView(itemView: View) { override fun bindView(itemView: View) {
view = itemView view = itemView
} }
protected fun <V : View> bind(id: Int): ReadOnlyProperty<KotlinEpoxyHolder, V> = protected fun <V : View> bind(id: Int): ReadOnlyProperty<RiotEpoxyHolder, V> =
Lazy { holder: KotlinEpoxyHolder, prop -> Lazy { holder: RiotEpoxyHolder, prop ->
holder.view.findViewById(id) as V? holder.view.findViewById(id) as V?
?: throw IllegalStateException("View ID $id for '${prop.name}' not found.") ?: throw IllegalStateException("View ID $id for '${prop.name}' not found.")
} }
@ -44,13 +44,13 @@ abstract class KotlinEpoxyHolder : EpoxyHolder() {
* https://github.com/JakeWharton/kotterknife * https://github.com/JakeWharton/kotterknife
*/ */
private class Lazy<V>( private class Lazy<V>(
private val initializer: (KotlinEpoxyHolder, KProperty<*>) -> V private val initializer: (RiotEpoxyHolder, KProperty<*>) -> V
) : ReadOnlyProperty<KotlinEpoxyHolder, V> { ) : ReadOnlyProperty<RiotEpoxyHolder, V> {
private object EMPTY private object EMPTY
private var value: Any? = EMPTY private var value: Any? = EMPTY
override fun getValue(thisRef: KotlinEpoxyHolder, property: KProperty<*>): V { override fun getValue(thisRef: RiotEpoxyHolder, property: KProperty<*>): V {
if (value == EMPTY) { if (value == EMPTY) {
value = initializer(thisRef, property) value = initializer(thisRef, property)
} }

View File

@ -0,0 +1,41 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotredesign.core.epoxy
import com.airbnb.epoxy.EpoxyModelWithHolder
import com.airbnb.epoxy.VisibilityState
abstract class RiotEpoxyModel<H : RiotEpoxyHolder> : EpoxyModelWithHolder<H>() {
private var onModelVisibilityStateChangedListener: OnVisibilityStateChangedListener? = null
override fun onVisibilityStateChanged(visibilityState: Int, view: H) {
onModelVisibilityStateChangedListener?.onVisibilityStateChanged(visibilityState)
super.onVisibilityStateChanged(visibilityState, view)
}
fun setOnVisibilityStateChanged(listener: OnVisibilityStateChangedListener): RiotEpoxyModel<H> {
this.onModelVisibilityStateChangedListener = listener
return this
}
interface OnVisibilityStateChangedListener {
fun onVisibilityStateChanged(@VisibilityState.Visibility visibilityState: Int)
}
}

View File

@ -14,25 +14,24 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.riotredesign.features.home.group package im.vector.riotredesign.core.utils
import arrow.core.Option
import com.jakewharton.rxrelay2.BehaviorRelay import com.jakewharton.rxrelay2.BehaviorRelay
import im.vector.matrix.android.api.session.group.model.GroupSummary
import io.reactivex.Observable import io.reactivex.Observable
class SelectedGroupHolder { open class RxStore<T>(defaultValue: T? = null) {
private val selectedGroupStream = BehaviorRelay.createDefault<Option<GroupSummary>>(Option.empty()) private val storeSubject: BehaviorRelay<T> = if (defaultValue == null) {
BehaviorRelay.create<T>()
fun setSelectedGroup(group: GroupSummary?) { } else {
val optionValue = Option.fromNullable(group) BehaviorRelay.createDefault(defaultValue)
selectedGroupStream.accept(optionValue)
} }
fun selectedGroup(): Observable<Option<GroupSummary>> { fun observe(): Observable<T> {
return selectedGroupStream.hide() return storeSubject.hide().distinctUntilChanged()
} }
fun post(value: T) {
} storeSubject.accept(value)
}
}

View File

@ -16,8 +16,8 @@
package im.vector.riotredesign.features.home package im.vector.riotredesign.features.home
import im.vector.riotredesign.features.home.group.SelectedGroupHolder import im.vector.riotredesign.features.home.group.SelectedGroupStore
import im.vector.riotredesign.features.home.room.VisibleRoomHolder import im.vector.riotredesign.features.home.room.VisibleRoomStore
import im.vector.riotredesign.features.home.room.detail.timeline.DefaultItemFactory import im.vector.riotredesign.features.home.room.detail.timeline.DefaultItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.MessageItemFactory import im.vector.riotredesign.features.home.room.detail.timeline.MessageItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.RoomMemberItemFactory import im.vector.riotredesign.features.home.room.detail.timeline.RoomMemberItemFactory
@ -80,11 +80,11 @@ class HomeModule {
} }
single { single {
SelectedGroupHolder() SelectedGroupStore()
} }
single { single {
VisibleRoomHolder() VisibleRoomStore()
} }
single { single {

View File

@ -16,6 +16,7 @@
package im.vector.riotredesign.features.home.group package im.vector.riotredesign.features.home.group
import arrow.core.Option
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.Matrix
@ -25,7 +26,7 @@ import im.vector.riotredesign.core.platform.RiotViewModel
import org.koin.android.ext.android.get import org.koin.android.ext.android.get
class GroupListViewModel(initialState: GroupListViewState, class GroupListViewModel(initialState: GroupListViewState,
private val selectedGroupHolder: SelectedGroupHolder, private val selectedGroupHolder: SelectedGroupStore,
private val session: Session private val session: Session
) : RiotViewModel<GroupListViewState>(initialState) { ) : RiotViewModel<GroupListViewState>(initialState) {
@ -34,7 +35,7 @@ class GroupListViewModel(initialState: GroupListViewState,
@JvmStatic @JvmStatic
override fun create(viewModelContext: ViewModelContext, state: GroupListViewState): GroupListViewModel? { override fun create(viewModelContext: ViewModelContext, state: GroupListViewState): GroupListViewModel? {
val currentSession = Matrix.getInstance().currentSession val currentSession = Matrix.getInstance().currentSession
val selectedGroupHolder = viewModelContext.activity.get<SelectedGroupHolder>() val selectedGroupHolder = viewModelContext.activity.get<SelectedGroupStore>()
return GroupListViewModel(state, selectedGroupHolder, currentSession) return GroupListViewModel(state, selectedGroupHolder, currentSession)
} }
} }
@ -46,7 +47,8 @@ class GroupListViewModel(initialState: GroupListViewState,
private fun observeState() { private fun observeState() {
subscribe { subscribe {
selectedGroupHolder.setSelectedGroup(it.selectedGroup) val selectedGroup = Option.fromNullable(it.selectedGroup)
selectedGroupHolder.post(selectedGroup)
} }
} }

View File

@ -32,14 +32,13 @@ class GroupSummaryController(private val callback: Callback? = null
} }
summaries.forEach { groupSummary -> summaries.forEach { groupSummary ->
val isSelected = groupSummary.groupId == selected?.groupId val isSelected = groupSummary.groupId == selected?.groupId
GroupSummaryItem( groupSummaryItem {
groupName = groupSummary.displayName, id(groupSummary.groupId)
avatarUrl = groupSummary.avatarUrl, groupName(groupSummary.displayName)
isSelected = isSelected, selected(isSelected)
listener = { callback?.onGroupSelected(groupSummary) } avatarUrl(groupSummary.avatarUrl)
) listener { callback?.onGroupSelected(groupSummary) }
.id(groupSummary.groupId) }
.addTo(this)
} }
} }

View File

@ -17,25 +17,32 @@
package im.vector.riotredesign.features.home.group package im.vector.riotredesign.features.home.group
import android.widget.ImageView import android.widget.ImageView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.KotlinModel import im.vector.riotredesign.core.epoxy.RiotEpoxyHolder
import im.vector.riotredesign.core.epoxy.RiotEpoxyModel
import im.vector.riotredesign.core.platform.CheckableFrameLayout import im.vector.riotredesign.core.platform.CheckableFrameLayout
import im.vector.riotredesign.features.home.AvatarRenderer import im.vector.riotredesign.features.home.AvatarRenderer
@EpoxyModelClass(layout = R.layout.item_group)
abstract class GroupSummaryItem : RiotEpoxyModel<GroupSummaryItem.Holder>() {
data class GroupSummaryItem( @EpoxyAttribute lateinit var groupName: CharSequence
val groupName: CharSequence, @EpoxyAttribute var avatarUrl: String? = null
val avatarUrl: String?, @EpoxyAttribute var selected: Boolean = false
val isSelected: Boolean, @EpoxyAttribute var listener: (() -> Unit)? = null
val listener: (() -> Unit)? = null
) : KotlinModel(R.layout.item_group) {
private val avatarImageView by bind<ImageView>(R.id.groupAvatarImageView) override fun bind(holder: Holder) {
private val rootView by bind<CheckableFrameLayout>(R.id.itemGroupLayout) super.bind(holder)
holder.rootView.isSelected = selected
override fun bind() { holder.rootView.setOnClickListener { listener?.invoke() }
rootView.isSelected = isSelected AvatarRenderer.render(avatarUrl, groupName.toString(), holder.avatarImageView)
rootView.setOnClickListener { listener?.invoke() }
AvatarRenderer.render(avatarUrl, groupName.toString(), avatarImageView)
} }
class Holder : RiotEpoxyHolder() {
val avatarImageView by bind<ImageView>(R.id.groupAvatarImageView)
val rootView by bind<CheckableFrameLayout>(R.id.itemGroupLayout)
}
} }

View File

@ -14,16 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.riotredesign.features.home.room.detail.timeline package im.vector.riotredesign.features.home.group
import im.vector.riotredesign.R import arrow.core.Option
import im.vector.riotredesign.core.epoxy.KotlinModel import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.riotredesign.core.utils.RxStore
class BlankItem class SelectedGroupStore : RxStore<Option<GroupSummary>>(Option.empty())
: KotlinModel(R.layout.item_timeline_event_blank) {
override fun bind() {
//no-op
}
}

View File

@ -16,21 +16,6 @@
package im.vector.riotredesign.features.home.room package im.vector.riotredesign.features.home.room
import com.jakewharton.rxrelay2.BehaviorRelay import im.vector.riotredesign.core.utils.RxStore
import io.reactivex.Observable
import io.reactivex.subjects.BehaviorSubject
class VisibleRoomHolder { class VisibleRoomStore : RxStore<String>()
private val visibleRoomStream = BehaviorRelay.create<String>()
fun setVisibleRoom(roomId: String) {
visibleRoomStream.accept(roomId)
}
fun visibleRoom(): Observable<String> {
return visibleRoomStream.hide()
}
}

View File

@ -26,14 +26,14 @@ import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
import im.vector.riotredesign.core.extensions.lastMinBy import im.vector.riotredesign.core.extensions.lastMinBy
import im.vector.riotredesign.core.platform.RiotViewModel import im.vector.riotredesign.core.platform.RiotViewModel
import im.vector.riotredesign.features.home.room.VisibleRoomHolder import im.vector.riotredesign.features.home.room.VisibleRoomStore
import io.reactivex.rxkotlin.subscribeBy import io.reactivex.rxkotlin.subscribeBy
import org.koin.android.ext.android.get import org.koin.android.ext.android.get
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class RoomDetailViewModel(initialState: RoomDetailViewState, class RoomDetailViewModel(initialState: RoomDetailViewState,
private val session: Session, private val session: Session,
private val visibleRoomHolder: VisibleRoomHolder private val visibleRoomHolder: VisibleRoomStore
) : RiotViewModel<RoomDetailViewState>(initialState) { ) : RiotViewModel<RoomDetailViewState>(initialState) {
private val room = session.getRoom(initialState.roomId)!! private val room = session.getRoom(initialState.roomId)!!
@ -47,7 +47,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
@JvmStatic @JvmStatic
override fun create(viewModelContext: ViewModelContext, state: RoomDetailViewState): RoomDetailViewModel? { override fun create(viewModelContext: ViewModelContext, state: RoomDetailViewState): RoomDetailViewModel? {
val currentSession = Matrix.getInstance().currentSession val currentSession = Matrix.getInstance().currentSession
val visibleRoomHolder = viewModelContext.activity.get<VisibleRoomHolder>() val visibleRoomHolder = viewModelContext.activity.get<VisibleRoomStore>()
return RoomDetailViewModel(state, currentSession, visibleRoomHolder) return RoomDetailViewModel(state, currentSession, visibleRoomHolder)
} }
} }
@ -78,7 +78,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
} }
private fun handleIsDisplayed() { private fun handleIsDisplayed() {
visibleRoomHolder.setVisibleRoom(roomId) visibleRoomHolder.post(roomId)
} }
private fun observeDisplayedEvents() { private fun observeDisplayedEvents() {

View File

@ -19,31 +19,33 @@ package im.vector.riotredesign.features.home.room.detail.timeline
import android.view.View import android.view.View
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.annotation.LayoutRes import im.vector.riotredesign.core.epoxy.RiotEpoxyHolder
import im.vector.riotredesign.core.epoxy.KotlinModel import im.vector.riotredesign.core.epoxy.RiotEpoxyModel
import im.vector.riotredesign.features.home.AvatarRenderer import im.vector.riotredesign.features.home.AvatarRenderer
abstract class AbsMessageItem(private val informationData: MessageInformationData, abstract class AbsMessageItem<H : AbsMessageItem.Holder> : RiotEpoxyModel<H>() {
@LayoutRes layoutRes: Int
) : KotlinModel(layoutRes) {
protected abstract val avatarImageView: ImageView abstract val informationData: MessageInformationData
protected abstract val memberNameView: TextView
protected abstract val timeView: TextView
override fun bind() { override fun bind(holder: H) {
if (informationData.showInformation) { if (informationData.showInformation) {
avatarImageView.visibility = View.VISIBLE holder.avatarImageView.visibility = View.VISIBLE
memberNameView.visibility = View.VISIBLE holder.memberNameView.visibility = View.VISIBLE
timeView.visibility = View.VISIBLE holder.timeView.visibility = View.VISIBLE
timeView.text = informationData.time holder.timeView.text = informationData.time
memberNameView.text = informationData.memberName holder.memberNameView.text = informationData.memberName
AvatarRenderer.render(informationData.avatarUrl, informationData.memberName?.toString(), avatarImageView) AvatarRenderer.render(informationData.avatarUrl, informationData.memberName?.toString(), holder.avatarImageView)
} else { } else {
avatarImageView.visibility = View.GONE holder.avatarImageView.visibility = View.GONE
memberNameView.visibility = View.GONE holder.memberNameView.visibility = View.GONE
timeView.visibility = View.GONE holder.timeView.visibility = View.GONE
} }
} }
abstract class Holder : RiotEpoxyHolder() {
abstract val avatarImageView: ImageView
abstract val memberNameView: TextView
abstract val timeView: TextView
}
} }

View File

@ -17,16 +17,22 @@
package im.vector.riotredesign.features.home.room.detail.timeline package im.vector.riotredesign.features.home.room.detail.timeline
import android.widget.TextView import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import com.airbnb.epoxy.EpoxyModelWithHolder
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.KotlinModel import im.vector.riotredesign.core.epoxy.RiotEpoxyHolder
data class DaySeparatorItem( @EpoxyModelClass(layout = R.layout.item_timeline_event_day_separator)
val formattedDay: CharSequence abstract class DaySeparatorItem : EpoxyModelWithHolder<DaySeparatorItem.Holder>() {
) : KotlinModel(R.layout.item_timeline_event_day_separator) {
private val dayTextView by bind<TextView>(R.id.itemDayTextView) @EpoxyAttribute lateinit var formattedDay: CharSequence
override fun bind() { override fun bind(holder: Holder) {
dayTextView.text = formattedDay holder.dayTextView.text = formattedDay
}
class Holder : RiotEpoxyHolder() {
val dayTextView by bind<TextView>(R.id.itemDayTextView)
} }
} }

View File

@ -17,16 +17,22 @@
package im.vector.riotredesign.features.home.room.detail.timeline package im.vector.riotredesign.features.home.room.detail.timeline
import android.widget.TextView import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.KotlinModel import im.vector.riotredesign.core.epoxy.RiotEpoxyHolder
import im.vector.riotredesign.core.epoxy.RiotEpoxyModel
class DefaultItem( @EpoxyModelClass(layout = R.layout.item_timeline_event_default)
val text: CharSequence? = null abstract class DefaultItem : RiotEpoxyModel<DefaultItem.Holder>() {
) : KotlinModel(R.layout.item_timeline_event_default) {
private val messageView by bind<TextView>(R.id.stateMessageView) @EpoxyAttribute var text: CharSequence? = null
override fun bind() { override fun bind(holder: Holder) {
messageView.text = text holder.messageView.text = text
}
class Holder : RiotEpoxyHolder() {
val messageView by bind<TextView>(R.id.stateMessageView)
} }
} }

View File

@ -26,7 +26,7 @@ class DefaultItemFactory {
} else { } else {
"an exception occurred when rendering the event ${event.root.eventId}" "an exception occurred when rendering the event ${event.root.eventId}"
} }
return DefaultItem(text = text) return DefaultItem_().text(text)
} }
} }

View File

@ -18,23 +18,27 @@ package im.vector.riotredesign.features.home.room.detail.timeline
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.features.media.MediaContentRenderer import im.vector.riotredesign.features.media.MediaContentRenderer
class MessageImageItem( @EpoxyModelClass(layout = R.layout.item_timeline_event_image_message)
private val mediaData: MediaContentRenderer.Data, abstract class MessageImageItem : AbsMessageItem<MessageImageItem.Holder>() {
informationData: MessageInformationData
) : AbsMessageItem(informationData, R.layout.item_timeline_event_image_message) {
override val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView) @EpoxyAttribute lateinit var mediaData: MediaContentRenderer.Data
override val memberNameView by bind<TextView>(R.id.messageMemberNameView) @EpoxyAttribute override lateinit var informationData: MessageInformationData
override val timeView by bind<TextView>(R.id.messageTimeView)
private val imageView by bind<ImageView>(R.id.messageImageView)
override fun bind() { override fun bind(holder: Holder) {
super.bind() super.bind(holder)
MediaContentRenderer.render(mediaData, MediaContentRenderer.Mode.THUMBNAIL, imageView) MediaContentRenderer.render(mediaData, MediaContentRenderer.Mode.THUMBNAIL, holder.imageView)
} }
class Holder : AbsMessageItem.Holder() {
override val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)
override val memberNameView by bind<TextView>(R.id.messageMemberNameView)
override val timeView by bind<TextView>(R.id.messageTimeView)
val imageView by bind<ImageView>(R.id.messageImageView)
}
} }

View File

@ -26,7 +26,7 @@ import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.core.epoxy.KotlinModel import im.vector.riotredesign.core.epoxy.RiotEpoxyModel
import im.vector.riotredesign.core.extensions.localDateTime import im.vector.riotredesign.core.extensions.localDateTime
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
import im.vector.riotredesign.features.media.MediaContentRenderer import im.vector.riotredesign.features.media.MediaContentRenderer
@ -39,7 +39,7 @@ class MessageItemFactory(private val timelineMediaSizeProvider: TimelineMediaSiz
fun create(event: TimelineEvent, fun create(event: TimelineEvent,
nextEvent: TimelineEvent?, nextEvent: TimelineEvent?,
callback: TimelineEventController.Callback? callback: TimelineEventController.Callback?
): KotlinModel? { ): RiotEpoxyModel<*>? {
val roomMember = event.roomMember val roomMember = event.roomMember
val nextRoomMember = nextEvent?.roomMember val nextRoomMember = nextEvent?.roomMember
@ -48,12 +48,12 @@ class MessageItemFactory(private val timelineMediaSizeProvider: TimelineMediaSiz
val nextDate = nextEvent?.root?.localDateTime() val nextDate = nextEvent?.root?.localDateTime()
val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate() val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60)) val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60))
?: false ?: false
if (addDaySeparator if (addDaySeparator
|| nextRoomMember != roomMember || nextRoomMember != roomMember
|| nextEvent?.root?.type != EventType.MESSAGE || nextEvent?.root?.type != EventType.MESSAGE
|| isNextMessageReceivedMoreThanOneHourAgo) { || isNextMessageReceivedMoreThanOneHourAgo) {
messagesDisplayedWithInformation.add(event.root.eventId) messagesDisplayedWithInformation.add(event.root.eventId)
} }
@ -71,9 +71,9 @@ class MessageItemFactory(private val timelineMediaSizeProvider: TimelineMediaSiz
} }
} }
private fun buildNotHandledMessageItem(messageContent: MessageContent): KotlinModel? { private fun buildNotHandledMessageItem(messageContent: MessageContent): DefaultItem? {
val text = "${messageContent.type} message events are not yet handled" val text = "${messageContent.type} message events are not yet handled"
return DefaultItem(text = text) return DefaultItem_().text(text)
} }
private fun buildImageMessageItem(messageContent: MessageImageContent, private fun buildImageMessageItem(messageContent: MessageImageContent,
@ -89,7 +89,9 @@ class MessageItemFactory(private val timelineMediaSizeProvider: TimelineMediaSiz
rotation = messageContent.info?.rotation, rotation = messageContent.info?.rotation,
orientation = messageContent.info?.orientation orientation = messageContent.info?.orientation
) )
return MessageImageItem(data, informationData) return MessageImageItem_()
.informationData(informationData)
.mediaData(data)
} }
private fun buildTextMessageItem(messageContent: MessageTextContent, private fun buildTextMessageItem(messageContent: MessageTextContent,
@ -106,10 +108,9 @@ class MessageItemFactory(private val timelineMediaSizeProvider: TimelineMediaSiz
Linkify.addLinks(spannable, Linkify.ALL) Linkify.addLinks(spannable, Linkify.ALL)
spannable spannable
} }
return MessageTextItem( return MessageTextItem_()
message = message, .message(message)
informationData = informationData .informationData(informationData)
)
} }

View File

@ -18,22 +18,29 @@ package im.vector.riotredesign.features.home.room.detail.timeline
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.matrix.android.api.permalinks.MatrixLinkify import im.vector.matrix.android.api.permalinks.MatrixLinkify
import im.vector.riotredesign.R import im.vector.riotredesign.R
class MessageTextItem( @EpoxyModelClass(layout = R.layout.item_timeline_event_text_message)
val message: CharSequence? = null, abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
informationData: MessageInformationData
) : AbsMessageItem(informationData, R.layout.item_timeline_event_text_message) {
override val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView) @EpoxyAttribute var message: CharSequence? = null
override val memberNameView by bind<TextView>(R.id.messageMemberNameView) @EpoxyAttribute override lateinit var informationData: MessageInformationData
override val timeView by bind<TextView>(R.id.messageTimeView)
private val messageView by bind<TextView>(R.id.messageTextView)
override fun bind() { override fun bind(holder: Holder) {
super.bind() super.bind(holder)
messageView.text = message holder.messageView.text = message
MatrixLinkify.addLinkMovementMethod(messageView) MatrixLinkify.addLinkMovementMethod(holder.messageView)
} }
class Holder : AbsMessageItem.Holder() {
override val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)
override val memberNameView by bind<TextView>(R.id.messageMemberNameView)
override val timeView by bind<TextView>(R.id.messageTimeView)
val messageView by bind<TextView>(R.id.messageTextView)
}
} }

View File

@ -18,20 +18,27 @@ package im.vector.riotredesign.features.home.room.detail.timeline
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.KotlinModel import im.vector.riotredesign.core.epoxy.RiotEpoxyHolder
import im.vector.riotredesign.core.epoxy.RiotEpoxyModel
import im.vector.riotredesign.features.home.AvatarRenderer import im.vector.riotredesign.features.home.AvatarRenderer
class NoticeItem(private val noticeText: CharSequence? = null, @EpoxyModelClass(layout = R.layout.item_timeline_event_notice)
private val avatarUrl: String?, abstract class NoticeItem : RiotEpoxyModel<NoticeItem.Holder>() {
private val memberName: CharSequence? = null)
: KotlinModel(R.layout.item_timeline_event_notice) {
private val avatarImageView by bind<ImageView>(R.id.itemNoticeAvatarView) @EpoxyAttribute var noticeText: CharSequence? = null
private val noticeTextView by bind<TextView>(R.id.itemNoticeTextView) @EpoxyAttribute var avatarUrl: String? = null
@EpoxyAttribute var memberName: CharSequence? = null
override fun bind() { override fun bind(holder: Holder) {
noticeTextView.text = noticeText holder.noticeTextView.text = noticeText
AvatarRenderer.render(avatarUrl, memberName?.toString(), avatarImageView) AvatarRenderer.render(avatarUrl, memberName?.toString(), holder.avatarImageView)
}
class Holder : RiotEpoxyHolder() {
val avatarImageView by bind<ImageView>(R.id.itemNoticeAvatarView)
val noticeTextView by bind<TextView>(R.id.itemNoticeTextView)
} }
} }

View File

@ -17,10 +17,10 @@
package im.vector.riotredesign.features.home.room.detail.timeline package im.vector.riotredesign.features.home.room.detail.timeline
import android.text.TextUtils import android.text.TextUtils
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.core.resources.StringProvider
@ -31,7 +31,10 @@ class RoomMemberItemFactory(private val stringProvider: StringProvider) {
fun create(event: TimelineEvent): NoticeItem? { fun create(event: TimelineEvent): NoticeItem? {
val roomMember = event.roomMember ?: return null val roomMember = event.roomMember ?: return null
val noticeText = buildRoomMemberNotice(event) ?: return null val noticeText = buildRoomMemberNotice(event) ?: return null
return NoticeItem(noticeText, roomMember.avatarUrl, roomMember.displayName) return NoticeItem_()
.noticeText(noticeText)
.avatarUrl(roomMember.avatarUrl)
.memberName(roomMember.displayName)
} }
private fun buildRoomMemberNotice(event: TimelineEvent): String? { private fun buildRoomMemberNotice(event: TimelineEvent): String? {

View File

@ -17,9 +17,9 @@
package im.vector.riotredesign.features.home.room.detail.timeline package im.vector.riotredesign.features.home.room.detail.timeline
import android.text.TextUtils import android.text.TextUtils
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.RoomNameContent import im.vector.matrix.android.api.session.room.model.RoomNameContent
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.core.resources.StringProvider
@ -37,7 +37,10 @@ class RoomNameItemFactory(private val stringProvider: StringProvider) {
} else { } else {
stringProvider.getString(R.string.notice_room_name_removed, roomMember.displayName) stringProvider.getString(R.string.notice_room_name_removed, roomMember.displayName)
} }
return NoticeItem(text, roomMember.avatarUrl, roomMember.displayName) return NoticeItem_()
.noticeText(text)
.avatarUrl(roomMember.avatarUrl)
.memberName(roomMember.displayName)
} }

View File

@ -36,7 +36,10 @@ class RoomTopicItemFactory(private val stringProvider: StringProvider) {
} else { } else {
stringProvider.getString(R.string.notice_room_topic_changed, roomMember.displayName, content.topic) stringProvider.getString(R.string.notice_room_topic_changed, roomMember.displayName, content.topic)
} }
return NoticeItem(text, roomMember.avatarUrl, roomMember.displayName) return NoticeItem_()
.noticeText(text)
.avatarUrl(roomMember.avatarUrl)
.memberName(roomMember.displayName)
} }

View File

@ -16,16 +16,14 @@
package im.vector.riotredesign.features.home.room.detail.timeline package im.vector.riotredesign.features.home.room.detail.timeline
import android.view.View
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.airbnb.epoxy.EpoxyAsyncUtil import com.airbnb.epoxy.EpoxyAsyncUtil
import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.OnModelVisibilityStateChangedListener
import com.airbnb.epoxy.VisibilityState import com.airbnb.epoxy.VisibilityState
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.timeline.TimelineData import im.vector.matrix.android.api.session.room.timeline.TimelineData
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.core.epoxy.KotlinModel import im.vector.riotredesign.core.epoxy.RiotEpoxyModel
import im.vector.riotredesign.core.extensions.localDateTime import im.vector.riotredesign.core.extensions.localDateTime
import im.vector.riotredesign.features.home.LoadingItemModel_ import im.vector.riotredesign.features.home.LoadingItemModel_
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
@ -78,16 +76,12 @@ class TimelineEventController(private val roomId: String,
timelineItemFactory.create(event, nextEvent, callback)?.also { timelineItemFactory.create(event, nextEvent, callback)?.also {
it.id(event.localId) it.id(event.localId)
it.setOnVisibilityStateChanged(OnModelVisibilityStateChangedListener<KotlinModel, View> { model, view, visibilityState -> it.setOnVisibilityStateChanged(TimelineEventVisibilityStateChangedListener(callback, event, currentPosition))
if (visibilityState == VisibilityState.VISIBLE) {
callback?.onEventVisible(event, currentPosition)
}
})
epoxyModels.add(it) epoxyModels.add(it)
} }
if (addDaySeparator) { if (addDaySeparator) {
val formattedDay = dateFormatter.formatMessageDay(date) val formattedDay = dateFormatter.formatMessageDay(date)
val daySeparatorItem = DaySeparatorItem(formattedDay).id(roomId + formattedDay) val daySeparatorItem = DaySeparatorItem_().formattedDay(formattedDay).id(roomId + formattedDay)
epoxyModels.add(daySeparatorItem) epoxyModels.add(daySeparatorItem)
} }
return epoxyModels return epoxyModels
@ -111,4 +105,18 @@ class TimelineEventController(private val roomId: String,
fun onUrlClicked(url: String) fun onUrlClicked(url: String)
} }
}
private class TimelineEventVisibilityStateChangedListener(private val callback: TimelineEventController.Callback?,
private val event: TimelineEvent,
private val currentPosition: Int)
: RiotEpoxyModel.OnVisibilityStateChangedListener {
override fun onVisibilityStateChanged(visibilityState: Int) {
if (visibilityState == VisibilityState.VISIBLE) {
callback?.onEventVisible(event, currentPosition)
}
}
} }

View File

@ -18,7 +18,7 @@ package im.vector.riotredesign.features.home.room.detail.timeline
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.core.epoxy.KotlinModel import im.vector.riotredesign.core.epoxy.RiotEpoxyModel
class TimelineItemFactory(private val messageItemFactory: MessageItemFactory, class TimelineItemFactory(private val messageItemFactory: MessageItemFactory,
private val roomNameItemFactory: RoomNameItemFactory, private val roomNameItemFactory: RoomNameItemFactory,
@ -28,7 +28,7 @@ class TimelineItemFactory(private val messageItemFactory: MessageItemFactory,
fun create(event: TimelineEvent, fun create(event: TimelineEvent,
nextEvent: TimelineEvent?, nextEvent: TimelineEvent?,
callback: TimelineEventController.Callback?): KotlinModel? { callback: TimelineEventController.Callback?): RiotEpoxyModel<*>? {
return try { return try {
when (event.root.type) { when (event.root.type) {

View File

@ -20,33 +20,39 @@ import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat import androidx.core.graphics.drawable.DrawableCompat
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.KotlinModel import im.vector.riotredesign.core.epoxy.RiotEpoxyHolder
import im.vector.riotredesign.core.epoxy.RiotEpoxyModel
data class RoomCategoryItem( @EpoxyModelClass(layout = R.layout.item_room_category)
val title: CharSequence, abstract class RoomCategoryItem : RiotEpoxyModel<RoomCategoryItem.Holder>() {
val isExpanded: Boolean,
val unreadCount: Int,
val showHighlighted: Boolean,
val listener: (() -> Unit)? = null
) : KotlinModel(R.layout.item_room_category) {
private val unreadCounterBadgeView by bind<UnreadCounterBadgeView>(R.id.roomCategoryUnreadCounterBadgeView) @EpoxyAttribute lateinit var title: CharSequence
private val titleView by bind<TextView>(R.id.roomCategoryTitleView) @EpoxyAttribute var expanded: Boolean = false
private val rootView by bind<ViewGroup>(R.id.roomCategoryRootView) @EpoxyAttribute var unreadCount: Int = 0
@EpoxyAttribute var showHighlighted: Boolean = false
@EpoxyAttribute var listener: (() -> Unit)? = null
private val tintColor by lazy { override fun bind(holder: Holder) {
ContextCompat.getColor(rootView.context, R.color.bluey_grey_two) val tintColor = ContextCompat.getColor(holder.rootView.context, R.color.bluey_grey_two)
} val expandedArrowDrawableRes = if (expanded) R.drawable.ic_expand_more_white else R.drawable.ic_expand_less_white
val expandedArrowDrawable = ContextCompat.getDrawable(holder.rootView.context, expandedArrowDrawableRes)?.also {
override fun bind() {
val expandedArrowDrawableRes = if (isExpanded) R.drawable.ic_expand_more_white else R.drawable.ic_expand_less_white
val expandedArrowDrawable = ContextCompat.getDrawable(rootView.context, expandedArrowDrawableRes)?.also {
DrawableCompat.setTint(it, tintColor) DrawableCompat.setTint(it, tintColor)
} }
unreadCounterBadgeView.render(unreadCount, showHighlighted) holder.unreadCounterBadgeView.render(unreadCount, showHighlighted)
titleView.setCompoundDrawablesWithIntrinsicBounds(expandedArrowDrawable, null, null, null) holder.titleView.setCompoundDrawablesWithIntrinsicBounds(expandedArrowDrawable, null, null, null)
titleView.text = title holder.titleView.text = title
rootView.setOnClickListener { listener?.invoke() } holder.rootView.setOnClickListener { listener?.invoke() }
} }
class Holder : RiotEpoxyHolder() {
val unreadCounterBadgeView by bind<UnreadCounterBadgeView>(R.id.roomCategoryUnreadCounterBadgeView)
val titleView by bind<TextView>(R.id.roomCategoryTitleView)
val rootView by bind<ViewGroup>(R.id.roomCategoryRootView)
}
} }

View File

@ -27,8 +27,8 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.tag.RoomTag import im.vector.matrix.android.api.session.room.model.tag.RoomTag
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
import im.vector.riotredesign.core.platform.RiotViewModel import im.vector.riotredesign.core.platform.RiotViewModel
import im.vector.riotredesign.features.home.group.SelectedGroupHolder import im.vector.riotredesign.features.home.group.SelectedGroupStore
import im.vector.riotredesign.features.home.room.VisibleRoomHolder import im.vector.riotredesign.features.home.room.VisibleRoomStore
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.functions.Function3 import io.reactivex.functions.Function3
import io.reactivex.rxkotlin.subscribeBy import io.reactivex.rxkotlin.subscribeBy
@ -39,8 +39,8 @@ typealias RoomListFilterName = CharSequence
class RoomListViewModel(initialState: RoomListViewState, class RoomListViewModel(initialState: RoomListViewState,
private val session: Session, private val session: Session,
private val selectedGroupHolder: SelectedGroupHolder, private val selectedGroupHolder: SelectedGroupStore,
private val visibleRoomHolder: VisibleRoomHolder, private val visibleRoomHolder: VisibleRoomStore,
private val roomSelectionRepository: RoomSelectionRepository, private val roomSelectionRepository: RoomSelectionRepository,
private val roomSummaryComparator: RoomSummaryComparator) private val roomSummaryComparator: RoomSummaryComparator)
: RiotViewModel<RoomListViewState>(initialState) { : RiotViewModel<RoomListViewState>(initialState) {
@ -51,8 +51,8 @@ class RoomListViewModel(initialState: RoomListViewState,
override fun create(viewModelContext: ViewModelContext, state: RoomListViewState): RoomListViewModel? { override fun create(viewModelContext: ViewModelContext, state: RoomListViewState): RoomListViewModel? {
val currentSession = Matrix.getInstance().currentSession val currentSession = Matrix.getInstance().currentSession
val roomSelectionRepository = viewModelContext.activity.get<RoomSelectionRepository>() val roomSelectionRepository = viewModelContext.activity.get<RoomSelectionRepository>()
val selectedGroupHolder = viewModelContext.activity.get<SelectedGroupHolder>() val selectedGroupHolder = viewModelContext.activity.get<SelectedGroupStore>()
val visibleRoomHolder = viewModelContext.activity.get<VisibleRoomHolder>() val visibleRoomHolder = viewModelContext.activity.get<VisibleRoomStore>()
val roomSummaryComparator = viewModelContext.activity.get<RoomSummaryComparator>() val roomSummaryComparator = viewModelContext.activity.get<RoomSummaryComparator>()
return RoomListViewModel(state, currentSession, selectedGroupHolder, visibleRoomHolder, roomSelectionRepository, roomSummaryComparator) return RoomListViewModel(state, currentSession, selectedGroupHolder, visibleRoomHolder, roomSelectionRepository, roomSummaryComparator)
} }
@ -87,17 +87,18 @@ class RoomListViewModel(initialState: RoomListViewState,
} }
private fun observeVisibleRoom() { private fun observeVisibleRoom() {
visibleRoomHolder.visibleRoom() visibleRoomHolder.observe()
.subscribeBy { .doOnNext {
setState { copy(selectedRoomId = it) } setState { copy(selectedRoomId = it) }
} }
.subscribe()
.disposeOnClear() .disposeOnClear()
} }
private fun observeRoomSummaries() { private fun observeRoomSummaries() {
Observable.combineLatest<List<RoomSummary>, Option<GroupSummary>, Option<RoomListFilterName>, RoomSummaries>( Observable.combineLatest<List<RoomSummary>, Option<GroupSummary>, Option<RoomListFilterName>, RoomSummaries>(
session.rx().liveRoomSummaries().throttleLast(300, TimeUnit.MILLISECONDS), session.rx().liveRoomSummaries().throttleLast(300, TimeUnit.MILLISECONDS),
selectedGroupHolder.selectedGroup(), selectedGroupHolder.observe(),
roomListFilter.throttleLast(300, TimeUnit.MILLISECONDS), roomListFilter.throttleLast(300, TimeUnit.MILLISECONDS),
Function3 { rooms, selectedGroupOption, filterRoomOption -> Function3 { rooms, selectedGroupOption, filterRoomOption ->
val filteredRooms = filterRooms(rooms, filterRoomOption) val filteredRooms = filterRooms(rooms, filterRoomOption)

View File

@ -85,18 +85,17 @@ class RoomSummaryController(private val stringProvider: StringProvider
summaries.map { it.notificationCount }.reduce { acc, i -> acc + i } summaries.map { it.notificationCount }.reduce { acc, i -> acc + i }
} }
val showHighlighted = summaries.any { it.highlightCount > 0 } val showHighlighted = summaries.any { it.highlightCount > 0 }
RoomCategoryItem( roomCategoryItem {
title = stringProvider.getString(titleRes).toUpperCase(), id(titleRes)
isExpanded = isExpanded, title(stringProvider.getString(titleRes).toUpperCase())
unreadCount = unreadCount, expanded(isExpanded)
showHighlighted = showHighlighted, unreadCount(unreadCount)
listener = { showHighlighted(showHighlighted)
mutateExpandedState() listener {
setData(viewState) mutateExpandedState()
} setData(viewState)
) }
.id(titleRes) }
.addTo(this)
} }
private fun buildRoomModels(summaries: List<RoomSummary>, selectedRoomId: String?) { private fun buildRoomModels(summaries: List<RoomSummary>, selectedRoomId: String?) {
@ -104,16 +103,16 @@ class RoomSummaryController(private val stringProvider: StringProvider
val unreadCount = roomSummary.notificationCount val unreadCount = roomSummary.notificationCount
val showHighlighted = roomSummary.highlightCount > 0 val showHighlighted = roomSummary.highlightCount > 0
val isSelected = roomSummary.roomId == selectedRoomId val isSelected = roomSummary.roomId == selectedRoomId
RoomSummaryItem(
roomName = roomSummary.displayName, roomSummaryItem {
avatarUrl = roomSummary.avatarUrl, id(roomSummary.roomId)
isSelected = isSelected, roomName(roomSummary.displayName)
showHighlighted = showHighlighted, avatarUrl(roomSummary.avatarUrl)
unreadCount = unreadCount, selected(isSelected)
listener = { callback?.onRoomSelected(roomSummary) } showHighlighted(showHighlighted)
) unreadCount(unreadCount)
.id(roomSummary.roomId) listener { callback?.onRoomSelected(roomSummary) }
.addTo(this) }
} }
} }

View File

@ -18,31 +18,40 @@ package im.vector.riotredesign.features.home.room.list
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.KotlinModel import im.vector.riotredesign.core.epoxy.RiotEpoxyHolder
import im.vector.riotredesign.core.epoxy.RiotEpoxyModel
import im.vector.riotredesign.core.platform.CheckableFrameLayout import im.vector.riotredesign.core.platform.CheckableFrameLayout
import im.vector.riotredesign.features.home.AvatarRenderer import im.vector.riotredesign.features.home.AvatarRenderer
data class RoomSummaryItem( @EpoxyModelClass(layout = R.layout.item_room)
val roomName: CharSequence, abstract class RoomSummaryItem : RiotEpoxyModel<RoomSummaryItem.Holder>() {
val avatarUrl: String?,
val isSelected: Boolean,
val unreadCount: Int,
val showHighlighted: Boolean,
val listener: (() -> Unit)? = null
) : KotlinModel(R.layout.item_room) {
private val unreadCounterBadgeView by bind<UnreadCounterBadgeView>(R.id.roomUnreadCounterBadgeView) @EpoxyAttribute lateinit var roomName: CharSequence
private val titleView by bind<TextView>(R.id.roomNameView) @EpoxyAttribute var avatarUrl: String? = null
private val avatarImageView by bind<ImageView>(R.id.roomAvatarImageView) @EpoxyAttribute var selected: Boolean = false
private val rootView by bind<CheckableFrameLayout>(R.id.itemRoomLayout) @EpoxyAttribute var unreadCount: Int = 0
@EpoxyAttribute var showHighlighted: Boolean = false
@EpoxyAttribute var listener: (() -> Unit)? = null
override fun bind() {
unreadCounterBadgeView.render(unreadCount, showHighlighted) override fun bind(holder: Holder) {
rootView.isChecked = isSelected super.bind(holder)
rootView.setOnClickListener { listener?.invoke() } holder.unreadCounterBadgeView.render(unreadCount, showHighlighted)
titleView.text = roomName holder.rootView.isChecked = selected
AvatarRenderer.render(avatarUrl, roomName.toString(), avatarImageView) holder.rootView.setOnClickListener { listener?.invoke() }
holder.titleView.text = roomName
AvatarRenderer.render(avatarUrl, roomName.toString(), holder.avatarImageView)
} }
class Holder : RiotEpoxyHolder() {
val unreadCounterBadgeView by bind<UnreadCounterBadgeView>(R.id.roomUnreadCounterBadgeView)
val titleView by bind<TextView>(R.id.roomNameView)
val avatarImageView by bind<ImageView>(R.id.roomAvatarImageView)
val rootView by bind<CheckableFrameLayout>(R.id.itemRoomLayout)
}
} }

View File

@ -95,6 +95,7 @@ dependencies {
// Logging // Logging
implementation 'com.jakewharton.timber:timber:4.7.1' implementation 'com.jakewharton.timber:timber:4.7.1'
implementation 'com.facebook.stetho:stetho-okhttp3:1.5.0'
debugImplementation 'com.airbnb.okreplay:okreplay:1.4.0' debugImplementation 'com.airbnb.okreplay:okreplay:1.4.0'
releaseImplementation 'com.airbnb.okreplay:noop:1.4.0' releaseImplementation 'com.airbnb.okreplay:noop:1.4.0'

View File

@ -16,6 +16,7 @@
package im.vector.matrix.android.internal.di package im.vector.matrix.android.internal.di
import com.facebook.stetho.okhttp3.StethoInterceptor
import im.vector.matrix.android.internal.network.AccessTokenInterceptor import im.vector.matrix.android.internal.network.AccessTokenInterceptor
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
import im.vector.matrix.android.internal.network.UnitConverterFactory import im.vector.matrix.android.internal.network.UnitConverterFactory
@ -47,11 +48,16 @@ class NetworkModule {
OkReplayInterceptor() OkReplayInterceptor()
} }
single {
StethoInterceptor()
}
single { single {
OkHttpClient.Builder() OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS) .connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS)
.addNetworkInterceptor(get<StethoInterceptor>())
.addInterceptor(get<AccessTokenInterceptor>()) .addInterceptor(get<AccessTokenInterceptor>())
.addInterceptor(get<HttpLoggingInterceptor>()) .addInterceptor(get<HttpLoggingInterceptor>())
.addInterceptor(get<OkReplayInterceptor>()) .addInterceptor(get<OkReplayInterceptor>())