Home: continue architecture rework. WIP

This commit is contained in:
ganfra 2019-05-16 19:14:02 +02:00 committed by Benoit Marty
parent 268730e71b
commit 275521db70
30 changed files with 416 additions and 346 deletions

View file

@ -18,6 +18,7 @@
package im.vector.matrix.android.api.session.user package im.vector.matrix.android.api.session.user
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.session.user.model.User
/** /**
@ -32,4 +33,11 @@ interface UserService {
*/ */
fun getUser(userId: String): User? fun getUser(userId: String): User?
/**
* Observe a live user from a userId
* @param userId the userId to look for.
* @return a Livedata of user with userId
*/
fun observeUser(userId: String): LiveData<User?>
} }

View file

@ -14,23 +14,22 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.riotredesign.features.home.room.list package im.vector.matrix.android.internal.database.mapper
import android.content.SharedPreferences import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.internal.database.model.UserEntity
private const val SHARED_PREFS_SELECTED_ROOM_KEY = "SHARED_PREFS_SELECTED_ROOM_KEY" internal object UserMapper {
class RoomSelectionRepository(private val sharedPreferences: SharedPreferences) { fun map(userEntity: UserEntity): User {
return User(
fun lastSelectedRoom(): String? { userEntity.userId,
return sharedPreferences.getString(SHARED_PREFS_SELECTED_ROOM_KEY, null) userEntity.displayName,
userEntity.avatarUrl
)
}
} }
fun saveLastSelectedRoom(roomId: String) { internal fun UserEntity.asDomain(): User {
sharedPreferences.edit() return UserMapper.map(this)
.putString(SHARED_PREFS_SELECTED_ROOM_KEY, roomId)
.apply()
} }
}

View file

@ -231,6 +231,11 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
return userService.getUser(userId) return userService.getUser(userId)
} }
override fun observeUser(userId: String): LiveData<User?> {
assert(isOpen)
return userService.observeUser(userId)
}
// Private methods ***************************************************************************** // Private methods *****************************************************************************
private fun assertMainThread() { private fun assertMainThread() {

View file

@ -18,9 +18,13 @@
package im.vector.matrix.android.internal.session.user package im.vector.matrix.android.internal.session.user
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.user.UserService import im.vector.matrix.android.api.session.user.UserService
import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.internal.database.RealmLiveData
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.UserEntity import im.vector.matrix.android.internal.database.model.UserEntity
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.util.fetchCopied import im.vector.matrix.android.internal.util.fetchCopied
@ -31,10 +35,17 @@ internal class DefaultUserService(private val monarchy: Monarchy) : UserService
val userEntity = monarchy.fetchCopied { UserEntity.where(it, userId).findFirst() } val userEntity = monarchy.fetchCopied { UserEntity.where(it, userId).findFirst() }
?: return null ?: return null
return User( return userEntity.asDomain()
userEntity.userId, }
userEntity.displayName,
userEntity.avatarUrl override fun observeUser(userId: String): LiveData<User?> {
) val liveRealmData = RealmLiveData(monarchy.realmConfiguration) { realm ->
UserEntity.where(realm, userId)
}
return Transformations.map(liveRealmData) { results ->
results
.map { it.asDomain() }
.firstOrNull()
}
} }
} }

View file

@ -24,8 +24,6 @@ import im.vector.riotredesign.core.resources.LocaleProvider
import im.vector.riotredesign.core.resources.StringArrayProvider import im.vector.riotredesign.core.resources.StringArrayProvider
import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.features.home.group.SelectedGroupStore import im.vector.riotredesign.features.home.group.SelectedGroupStore
import im.vector.riotredesign.features.home.room.VisibleRoomStore
import im.vector.riotredesign.features.home.room.list.RoomSelectionRepository
import im.vector.riotredesign.features.home.room.list.RoomSummaryComparator import im.vector.riotredesign.features.home.room.list.RoomSummaryComparator
import im.vector.riotredesign.features.notifications.NotificationDrawerManager import im.vector.riotredesign.features.notifications.NotificationDrawerManager
import org.koin.dsl.module.module import org.koin.dsl.module.module
@ -50,18 +48,10 @@ class AppModule(private val context: Context) {
context.getSharedPreferences("im.vector.riot", MODE_PRIVATE) context.getSharedPreferences("im.vector.riot", MODE_PRIVATE)
} }
single {
RoomSelectionRepository(get())
}
single { single {
SelectedGroupStore() SelectedGroupStore()
} }
single {
VisibleRoomStore()
}
single { single {
RoomSummaryComparator() RoomSummaryComparator()
} }
@ -78,6 +68,5 @@ class AppModule(private val context: Context) {
Matrix.getInstance().currentSession!! Matrix.getInstance().currentSession!!
} }
} }
} }

View file

@ -109,8 +109,6 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
override fun configure(toolbar: Toolbar) { override fun configure(toolbar: Toolbar) {
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
supportActionBar?.setHomeButtonEnabled(true)
supportActionBar?.setDisplayUseLogoEnabled(true)
} }
override fun getMenuRes() = R.menu.home override fun getMenuRes() = R.menu.home
@ -121,10 +119,6 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
drawerLayout.openDrawer(GravityCompat.START) drawerLayout.openDrawer(GravityCompat.START)
return true return true
} }
R.id.sliding_menu_settings -> {
startActivity(VectorSettingsActivity.getIntent(this, "TODO"))
return true
}
R.id.sliding_menu_sign_out -> { R.id.sliding_menu_sign_out -> {
SignOutUiWorker(this).perform(Matrix.getInstance().currentSession!!) SignOutUiWorker(this).perform(Matrix.getInstance().currentSession!!)
return true return true

View file

@ -18,26 +18,31 @@ package im.vector.riotredesign.features.home
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import arrow.core.Option
import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxState
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
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.sync.FilterService
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
import im.vector.riotredesign.core.platform.VectorViewModel import im.vector.riotredesign.core.platform.VectorViewModel
import im.vector.riotredesign.core.utils.LiveEvent import im.vector.riotredesign.features.home.group.ALL_COMMUNITIES_GROUP_ID
import im.vector.riotredesign.features.home.room.list.RoomSelectionRepository import im.vector.riotredesign.features.home.group.SelectedGroupStore
import io.reactivex.rxkotlin.subscribeBy import io.reactivex.Observable
import io.reactivex.functions.BiFunction
import org.koin.android.ext.android.get import org.koin.android.ext.android.get
import java.util.concurrent.TimeUnit
data class EmptyState(val isEmpty: Boolean = true) : MvRxState data class EmptyState(val isEmpty: Boolean = true) : MvRxState
class HomeActivityViewModel(state: EmptyState, class HomeActivityViewModel(state: EmptyState,
private val session: Session, private val session: Session,
roomSelectionRepository: RoomSelectionRepository private val selectedGroupStore: SelectedGroupStore,
private val homeRoomListStore: HomeRoomListObservableStore
) : VectorViewModel<EmptyState>(state), Session.Listener { ) : VectorViewModel<EmptyState>(state), Session.Listener {
companion object : MvRxViewModelFactory<HomeActivityViewModel, EmptyState> { companion object : MvRxViewModelFactory<HomeActivityViewModel, EmptyState> {
@ -45,8 +50,9 @@ class HomeActivityViewModel(state: EmptyState,
@JvmStatic @JvmStatic
override fun create(viewModelContext: ViewModelContext, state: EmptyState): HomeActivityViewModel? { override fun create(viewModelContext: ViewModelContext, state: EmptyState): HomeActivityViewModel? {
val session = Matrix.getInstance().currentSession!! val session = Matrix.getInstance().currentSession!!
val roomSelectionRepository = viewModelContext.activity.get<RoomSelectionRepository>() val selectedGroupStore = viewModelContext.activity.get<SelectedGroupStore>()
return HomeActivityViewModel(state, session, roomSelectionRepository) val homeRoomListObservableSource = viewModelContext.activity.get<HomeRoomListObservableStore>()
return HomeActivityViewModel(state, session, selectedGroupStore, homeRoomListObservableSource)
} }
} }
@ -54,29 +60,41 @@ class HomeActivityViewModel(state: EmptyState,
val isLoading: LiveData<Boolean> val isLoading: LiveData<Boolean>
get() = _isLoading get() = _isLoading
private val _openRoomLiveData = MutableLiveData<LiveEvent<String>>()
val openRoomLiveData: LiveData<LiveEvent<String>>
get() = _openRoomLiveData
init { init {
session.addListener(this) session.addListener(this)
val lastSelectedRoomId = roomSelectionRepository.lastSelectedRoom() observeRoomAndGroup()
if (lastSelectedRoomId == null || session.getRoom(lastSelectedRoomId) == null) { }
getTheFirstRoomWhenAvailable()
private fun observeRoomAndGroup() {
Observable
.combineLatest<List<RoomSummary>, Option<GroupSummary>, List<RoomSummary>>(
session.rx().liveRoomSummaries().throttleLast(300, TimeUnit.MILLISECONDS),
selectedGroupStore.observe(),
BiFunction { rooms, selectedGroupOption ->
val selectedGroup = selectedGroupOption.orNull()
val filteredDirectRooms = rooms
.filter { it.isDirect }
.filter {
if (selectedGroup == null || selectedGroup.groupId == ALL_COMMUNITIES_GROUP_ID) {
true
} else { } else {
_openRoomLiveData.postValue(LiveEvent(lastSelectedRoomId)) it.otherMemberIds
.intersect(selectedGroup.userIds)
.isNotEmpty()
} }
} }
private fun getTheFirstRoomWhenAvailable() { val filteredGroupRooms = rooms
session.rx().liveRoomSummaries() .filter { !it.isDirect }
.filter { it.isNotEmpty() } .filter {
.first(emptyList()) selectedGroup?.groupId == ALL_COMMUNITIES_GROUP_ID
.subscribeBy { || selectedGroup?.roomIds?.contains(it.roomId) ?: true
val firstRoom = it.firstOrNull()
if (firstRoom != null) {
_openRoomLiveData.postValue(LiveEvent(firstRoom.roomId))
} }
filteredDirectRooms + filteredGroupRooms
}
)
.subscribe {
homeRoomListStore.post(it)
} }
.disposeOnClear() .disposeOnClear()
} }
@ -87,8 +105,6 @@ class HomeActivityViewModel(state: EmptyState,
session.createRoom(createRoomParams, object : MatrixCallback<String> { session.createRoom(createRoomParams, object : MatrixCallback<String> {
override fun onSuccess(data: String) { override fun onSuccess(data: String) {
_isLoading.value = false _isLoading.value = false
// Open room id
_openRoomLiveData.postValue(LiveEvent(data))
} }
override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {

View file

@ -19,9 +19,11 @@ package im.vector.riotredesign.features.home
import android.os.Bundle import android.os.Bundle
import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.Matrix
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.observeK
import im.vector.riotredesign.core.extensions.replaceChildFragment import im.vector.riotredesign.core.extensions.replaceChildFragment
import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.features.home.group.GroupListFragment import im.vector.riotredesign.features.home.group.GroupListFragment
import im.vector.riotredesign.features.settings.VectorSettingsActivity
import kotlinx.android.synthetic.main.fragment_home_drawer.* import kotlinx.android.synthetic.main.fragment_home_drawer.*
class HomeDrawerFragment : VectorBaseFragment() { class HomeDrawerFragment : VectorBaseFragment() {
@ -42,12 +44,15 @@ class HomeDrawerFragment : VectorBaseFragment() {
replaceChildFragment(groupListFragment, R.id.homeDrawerGroupListContainer) replaceChildFragment(groupListFragment, R.id.homeDrawerGroupListContainer)
} }
val session = Matrix.getInstance().currentSession ?: return val session = Matrix.getInstance().currentSession ?: return
val user = session.getUser(session.sessionParams.credentials.userId) session.observeUser(session.sessionParams.credentials.userId).observeK(this) { user ->
if (user != null) { if (user != null) {
AvatarRenderer.render(user.avatarUrl, user.userId, user.displayName, homeDrawerHeaderAvatarView) AvatarRenderer.render(user.avatarUrl, user.userId, user.displayName, homeDrawerHeaderAvatarView)
homeDrawerUsernameView.text = user.displayName homeDrawerUsernameView.text = user.displayName
homeDrawerUserIdView.text = user.userId homeDrawerUserIdView.text = user.userId
} }
} }
homeDrawerHeaderSettingsView.setOnClickListener {
startActivity(VectorSettingsActivity.getIntent(requireContext(), "TODO"))
}
}
} }

View file

@ -25,7 +25,14 @@ import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserControl
import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserPresenter import im.vector.riotredesign.features.autocomplete.user.AutocompleteUserPresenter
import im.vector.riotredesign.features.home.group.GroupSummaryController import im.vector.riotredesign.features.home.group.GroupSummaryController
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotredesign.features.home.room.detail.timeline.factory.* import im.vector.riotredesign.features.home.room.detail.timeline.factory.CallItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.factory.DefaultItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.factory.MessageItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.factory.RoomHistoryVisibilityItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.factory.RoomMemberItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.factory.RoomNameItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.factory.RoomTopicItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.factory.TimelineItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter
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.home.room.list.RoomSummaryController import im.vector.riotredesign.features.home.room.list.RoomSummaryController
@ -49,6 +56,10 @@ class HomeModule {
HomeNavigator() HomeNavigator()
} }
scope(HOME_SCOPE) {
HomeRoomListObservableStore()
}
scope(HOME_SCOPE) { scope(HOME_SCOPE) {
HomePermalinkHandler(get()) HomePermalinkHandler(get())
} }

View file

@ -0,0 +1,22 @@
/*
* 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.features.home
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.riotredesign.core.utils.RxStore
class HomeRoomListObservableStore : RxStore<List<RoomSummary>>(emptyList())

View file

@ -54,15 +54,18 @@ class GroupListViewModel(initialState: GroupListViewState,
init { init {
observeGroupSummaries() observeGroupSummaries()
observeState() observeSelectionState()
} }
private fun observeState() { private fun observeSelectionState() {
subscribe { selectSubscribe(GroupListViewState::selectedGroup) {
val optionGroup = Option.fromNullable(it.selectedGroup) if (it != null) {
_openGroupLiveData.postValue(LiveEvent(it))
val optionGroup = Option.fromNullable(it)
selectedGroupHolder.post(optionGroup) selectedGroupHolder.post(optionGroup)
} }
} }
}
fun accept(action: GroupListActions) { fun accept(action: GroupListActions) {
when (action) { when (action) {
@ -75,7 +78,6 @@ class GroupListViewModel(initialState: GroupListViewState,
private fun handleSelectGroup(action: GroupListActions.SelectGroup) = withState { state -> private fun handleSelectGroup(action: GroupListActions.SelectGroup) = withState { state ->
if (state.selectedGroup?.groupId != action.groupSummary.groupId) { if (state.selectedGroup?.groupId != action.groupSummary.groupId) {
setState { copy(selectedGroup = action.groupSummary) } setState { copy(selectedGroup = action.groupSummary) }
_openGroupLiveData.postValue(LiveEvent(action.groupSummary))
} }
} }
@ -91,7 +93,8 @@ class GroupListViewModel(initialState: GroupListViewState,
listOf(allCommunityGroup) + it listOf(allCommunityGroup) + it
} }
.execute { async -> .execute { async ->
copy(asyncGroups = async) val newSelectedGroup = selectedGroup ?: async()?.firstOrNull()
copy(asyncGroups = async, selectedGroup = newSelectedGroup)
} }
} }

View file

@ -16,7 +16,6 @@
package im.vector.riotredesign.features.home.group package im.vector.riotredesign.features.home.group
import android.view.ViewGroup
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.EpoxyAttribute
@ -24,6 +23,7 @@ import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder
import im.vector.riotredesign.core.epoxy.VectorEpoxyModel import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
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) @EpoxyModelClass(layout = R.layout.item_group)
@ -39,13 +39,14 @@ abstract class GroupSummaryItem : VectorEpoxyModel<GroupSummaryItem.Holder>() {
super.bind(holder) super.bind(holder)
holder.rootView.setOnClickListener { listener?.invoke() } holder.rootView.setOnClickListener { listener?.invoke() }
holder.groupNameView.text = groupName holder.groupNameView.text = groupName
holder.rootView.isChecked = selected
AvatarRenderer.render(avatarUrl, groupId, groupName.toString(), holder.avatarImageView) AvatarRenderer.render(avatarUrl, groupId, groupName.toString(), holder.avatarImageView)
} }
class Holder : VectorEpoxyHolder() { class Holder : VectorEpoxyHolder() {
val avatarImageView by bind<ImageView>(R.id.groupAvatarImageView) val avatarImageView by bind<ImageView>(R.id.groupAvatarImageView)
val groupNameView by bind<TextView>(R.id.groupNameView) val groupNameView by bind<TextView>(R.id.groupNameView)
val rootView by bind<ViewGroup>(R.id.itemGroupLayout) val rootView by bind<CheckableFrameLayout>(R.id.itemGroupLayout)
} }
} }

View file

@ -18,15 +18,10 @@
package im.vector.riotredesign.features.home.group package im.vector.riotredesign.features.home.group
import android.graphics.drawable.Drawable
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import com.airbnb.mvrx.args import com.airbnb.mvrx.args
import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.transition.Transition
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.replaceChildFragment
import im.vector.riotredesign.core.glide.GlideApp
import im.vector.riotredesign.core.platform.ToolbarConfigurable import im.vector.riotredesign.core.platform.ToolbarConfigurable
import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.features.home.AvatarRenderer import im.vector.riotredesign.features.home.AvatarRenderer
@ -42,9 +37,12 @@ data class SelectedGroupParams(
val groupAvatar: String val groupAvatar: String
) : Parcelable ) : Parcelable
private const val CURRENT_DISPLAY_MODE = "CURRENT_DISPLAY_MODE"
class SelectedGroupFragment : VectorBaseFragment() { class SelectedGroupFragment : VectorBaseFragment() {
private val selectedGroupParams: SelectedGroupParams by args() private val selectedGroupParams: SelectedGroupParams by args()
private lateinit var currentDisplayMode: RoomListFragment.DisplayMode
override fun getLayoutResId(): Int { override fun getLayoutResId(): Int {
return R.layout.fragment_selected_group return R.layout.fragment_selected_group
@ -53,31 +51,36 @@ class SelectedGroupFragment : VectorBaseFragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
if (savedInstanceState == null) { if (savedInstanceState == null) {
updateSelectedFragment(RoomListFragment.DisplayMode.HOME) currentDisplayMode = RoomListFragment.DisplayMode.HOME
toolbar.setTitle(RoomListFragment.DisplayMode.HOME.titleRes) } else {
currentDisplayMode = savedInstanceState.getSerializable(CURRENT_DISPLAY_MODE) as? RoomListFragment.DisplayMode
?: RoomListFragment.DisplayMode.HOME
} }
renderState(currentDisplayMode)
setupBottomNavigationView() setupBottomNavigationView()
setupToolbar() setupToolbar()
} }
override fun onSaveInstanceState(outState: Bundle) {
outState.putSerializable(CURRENT_DISPLAY_MODE, currentDisplayMode)
super.onSaveInstanceState(outState)
}
private fun setupToolbar() { private fun setupToolbar() {
val parentActivity = vectorBaseActivity val parentActivity = vectorBaseActivity
if (parentActivity is ToolbarConfigurable) { if (parentActivity is ToolbarConfigurable) {
parentActivity.configure(toolbar) parentActivity.configure(groupToolbar)
}
val toolbarLogoTarget = object : SimpleTarget<Drawable>() {
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
toolbar.logo = resource
}
} }
groupToolbar.title = ""
AvatarRenderer.render( AvatarRenderer.render(
requireContext(),
GlideApp.with(this),
selectedGroupParams.groupAvatar, selectedGroupParams.groupAvatar,
selectedGroupParams.groupId, selectedGroupParams.groupId,
selectedGroupParams.groupName, selectedGroupParams.groupName,
toolbarLogoTarget groupToolbarAvatarImageView
) )
groupToolbarAvatarImageView.setOnClickListener {
}
} }
private fun setupBottomNavigationView() { private fun setupBottomNavigationView() {
@ -87,16 +90,29 @@ class SelectedGroupFragment : VectorBaseFragment() {
it.itemId == R.id.bottom_action_rooms -> RoomListFragment.DisplayMode.ROOMS it.itemId == R.id.bottom_action_rooms -> RoomListFragment.DisplayMode.ROOMS
else -> RoomListFragment.DisplayMode.HOME else -> RoomListFragment.DisplayMode.HOME
} }
updateSelectedFragment(displayMode) if (currentDisplayMode != displayMode) {
toolbar.setTitle(displayMode.titleRes) currentDisplayMode = displayMode
renderState(displayMode)
}
true true
} }
} }
private fun renderState(displayMode: RoomListFragment.DisplayMode) {
groupToolbarTitleView.setText(displayMode.titleRes)
updateSelectedFragment(displayMode)
}
private fun updateSelectedFragment(displayMode: RoomListFragment.DisplayMode) { private fun updateSelectedFragment(displayMode: RoomListFragment.DisplayMode) {
val roomListParams = RoomListParams(displayMode) val fragmentTag = "FRAGMENT_TAG_${displayMode.name}"
val roomListFragment = RoomListFragment.newInstance(roomListParams) var fragment = childFragmentManager.findFragmentByTag(fragmentTag)
replaceChildFragment(roomListFragment, R.id.roomListContainer) if (fragment == null) {
fragment = RoomListFragment.newInstance(RoomListParams(displayMode))
}
childFragmentManager.beginTransaction()
.replace(R.id.roomListContainer, fragment, fragmentTag)
.addToBackStack(fragmentTag)
.commit()
} }
companion object { companion object {

View file

@ -25,7 +25,6 @@ sealed class RoomDetailActions {
data class SendMessage(val text: String) : RoomDetailActions() data class SendMessage(val text: String) : RoomDetailActions()
data class SendMedia(val mediaFiles: List<MediaFile>) : RoomDetailActions() data class SendMedia(val mediaFiles: List<MediaFile>) : RoomDetailActions()
object IsDisplayed : RoomDetailActions()
data class EventDisplayed(val event: TimelineEvent) : RoomDetailActions() data class EventDisplayed(val event: TimelineEvent) : RoomDetailActions()
data class LoadMore(val direction: Timeline.Direction) : RoomDetailActions() data class LoadMore(val direction: Timeline.Direction) : RoomDetailActions()
data class SendReaction(val reaction: String, val targetEventId: String) : RoomDetailActions() data class SendReaction(val reaction: String, val targetEventId: String) : RoomDetailActions()

View file

@ -21,11 +21,13 @@ package im.vector.riotredesign.features.home.room.detail
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 androidx.appcompat.widget.Toolbar
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.replaceFragment import im.vector.riotredesign.core.extensions.replaceFragment
import im.vector.riotredesign.core.platform.ToolbarConfigurable
import im.vector.riotredesign.core.platform.VectorBaseActivity import im.vector.riotredesign.core.platform.VectorBaseActivity
class RoomDetailActivity : VectorBaseActivity() { class RoomDetailActivity : VectorBaseActivity(), ToolbarConfigurable {
override fun getLayoutRes(): Int { override fun getLayoutRes(): Int {
return R.layout.activity_room_detail return R.layout.activity_room_detail
@ -41,6 +43,14 @@ class RoomDetailActivity : VectorBaseActivity() {
} }
} }
override fun configure(toolbar: Toolbar) {
setSupportActionBar(toolbar)
supportActionBar?.let {
it.setDisplayShowHomeEnabled(true)
it.setDisplayHomeAsUpEnabled(true)
}
}
companion object { companion object {
private const val EXTRA_ROOM_DETAIL_ARGS = "EXTRA_ROOM_DETAIL_ARGS" private const val EXTRA_ROOM_DETAIL_ARGS = "EXTRA_ROOM_DETAIL_ARGS"

View file

@ -64,6 +64,7 @@ import im.vector.riotredesign.core.epoxy.LayoutManagerStateRestorer
import im.vector.riotredesign.core.extensions.hideKeyboard import im.vector.riotredesign.core.extensions.hideKeyboard
import im.vector.riotredesign.core.extensions.observeEvent import im.vector.riotredesign.core.extensions.observeEvent
import im.vector.riotredesign.core.glide.GlideApp import im.vector.riotredesign.core.glide.GlideApp
import im.vector.riotredesign.core.platform.ToolbarConfigurable
import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.core.utils.* import im.vector.riotredesign.core.utils.*
import im.vector.riotredesign.features.autocomplete.command.AutocompleteCommandPresenter import im.vector.riotredesign.features.autocomplete.command.AutocompleteCommandPresenter
@ -168,6 +169,7 @@ class RoomDetailFragment :
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
actionViewModel = ViewModelProviders.of(requireActivity()).get(ActionsHandler::class.java) actionViewModel = ViewModelProviders.of(requireActivity()).get(ActionsHandler::class.java)
bindScope(getOrCreateScope(HomeModule.ROOM_DETAIL_SCOPE)) bindScope(getOrCreateScope(HomeModule.ROOM_DETAIL_SCOPE))
setupToolbar()
setupRecyclerView() setupRecyclerView()
setupComposer() setupComposer()
setupAttachmentButton() setupAttachmentButton()
@ -187,6 +189,13 @@ class RoomDetailFragment :
}) })
} }
private fun setupToolbar() {
val parentActivity = vectorBaseActivity
if (parentActivity is ToolbarConfigurable) {
parentActivity.configure(roomToolbar)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK && data != null) { if (resultCode == RESULT_OK && data != null) {
@ -204,11 +213,6 @@ class RoomDetailFragment :
} }
} }
override fun onResume() {
super.onResume()
roomDetailViewModel.process(RoomDetailActions.IsDisplayed)
}
// PRIVATE METHODS ***************************************************************************** // PRIVATE METHODS *****************************************************************************
private fun setupRecyclerView() { private fun setupRecyclerView() {
@ -399,13 +403,13 @@ class RoomDetailFragment :
private fun renderRoomSummary(state: RoomDetailViewState) { private fun renderRoomSummary(state: RoomDetailViewState) {
state.asyncRoomSummary()?.let { state.asyncRoomSummary()?.let {
toolbarTitleView.text = it.displayName roomToolbarTitleView.text = it.displayName
AvatarRenderer.render(it, toolbarAvatarImageView) AvatarRenderer.render(it, roomToolbarAvatarImageView)
if (it.topic.isNotEmpty()) { if (it.topic.isNotEmpty()) {
toolbarSubtitleView.visibility = View.VISIBLE roomToolbarSubtitleView.visibility = View.VISIBLE
toolbarSubtitleView.text = it.topic roomToolbarSubtitleView.text = it.topic
} else { } else {
toolbarSubtitleView.visibility = View.GONE roomToolbarSubtitleView.visibility = View.GONE
} }
} }
} }

View file

@ -33,7 +33,6 @@ import im.vector.riotredesign.core.platform.VectorViewModel
import im.vector.riotredesign.core.utils.LiveEvent import im.vector.riotredesign.core.utils.LiveEvent
import im.vector.riotredesign.features.command.CommandParser import im.vector.riotredesign.features.command.CommandParser
import im.vector.riotredesign.features.command.ParsedCommand import im.vector.riotredesign.features.command.ParsedCommand
import im.vector.riotredesign.features.home.room.VisibleRoomStore
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDisplayableEvents import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDisplayableEvents
import io.reactivex.rxkotlin.subscribeBy import io.reactivex.rxkotlin.subscribeBy
import org.koin.android.ext.android.get import org.koin.android.ext.android.get
@ -42,8 +41,7 @@ import java.util.*
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: VisibleRoomStore
) : VectorViewModel<RoomDetailViewState>(initialState) { ) : VectorViewModel<RoomDetailViewState>(initialState) {
private val room = session.getRoom(initialState.roomId)!! private val room = session.getRoom(initialState.roomId)!!
@ -59,8 +57,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 = viewModelContext.activity.get<Session>() val currentSession = viewModelContext.activity.get<Session>()
val visibleRoomHolder = viewModelContext.activity.get<VisibleRoomStore>() return RoomDetailViewModel(state, currentSession)
return RoomDetailViewModel(state, currentSession, visibleRoomHolder)
} }
} }
@ -76,7 +73,6 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
fun process(action: RoomDetailActions) { fun process(action: RoomDetailActions) {
when (action) { when (action) {
is RoomDetailActions.SendMessage -> handleSendMessage(action) is RoomDetailActions.SendMessage -> handleSendMessage(action)
is RoomDetailActions.IsDisplayed -> handleIsDisplayed()
is RoomDetailActions.SendMedia -> handleSendMedia(action) is RoomDetailActions.SendMedia -> handleSendMedia(action)
is RoomDetailActions.EventDisplayed -> handleEventDisplayed(action) is RoomDetailActions.EventDisplayed -> handleEventDisplayed(action)
is RoomDetailActions.LoadMore -> handleLoadMore(action) is RoomDetailActions.LoadMore -> handleLoadMore(action)
@ -255,10 +251,6 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
displayedEventsObservable.accept(action) displayedEventsObservable.accept(action)
} }
private fun handleIsDisplayed() {
visibleRoomHolder.post(roomId)
}
private fun handleLoadMore(action: RoomDetailActions.LoadMore) { private fun handleLoadMore(action: RoomDetailActions.LoadMore) {
timeline.paginate(action.direction, PAGINATION_COUNT) timeline.paginate(action.direction, PAGINATION_COUNT)
} }

View file

@ -23,6 +23,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.Incomplete
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
@ -61,6 +62,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback {
} }
} }
private val roomListParams: RoomListParams by args()
private val roomController by inject<RoomSummaryController>() private val roomController by inject<RoomSummaryController>()
private val homeNavigator by inject<HomeNavigator>() private val homeNavigator by inject<HomeNavigator>()
private val roomListViewModel: RoomListViewModel by fragmentViewModel() private val roomListViewModel: RoomListViewModel by fragmentViewModel()
@ -71,12 +73,20 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
bindScope(getOrCreateScope(HomeModule.ROOM_LIST_SCOPE)) bindScope(getOrCreateScope(HomeModule.ROOM_LIST_SCOPE))
setupRecyclerView() setupRecyclerView()
setupCreateRoomButton()
roomListViewModel.subscribe { renderState(it) } roomListViewModel.subscribe { renderState(it) }
roomListViewModel.openRoomLiveData.observeEvent(this) { roomListViewModel.openRoomLiveData.observeEvent(this) {
homeNavigator.openRoomDetail(it, null) homeNavigator.openRoomDetail(it, null)
} }
} }
private fun setupCreateRoomButton() {
createRoomButton.setImageResource(R.drawable.ic_add_white)
createRoomButton.setOnClickListener {
vectorBaseActivity.notImplemented()
}
}
private fun setupRecyclerView() { private fun setupRecyclerView() {
val layoutManager = LinearLayoutManager(context) val layoutManager = LinearLayoutManager(context)
val stateRestorer = LayoutManagerStateRestorer(layoutManager).register() val stateRestorer = LayoutManagerStateRestorer(layoutManager).register()

View file

@ -23,28 +23,19 @@ import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import com.jakewharton.rxrelay2.BehaviorRelay import com.jakewharton.rxrelay2.BehaviorRelay
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.group.model.GroupSummary
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.RoomSummary 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.riotredesign.core.platform.VectorViewModel import im.vector.riotredesign.core.platform.VectorViewModel
import im.vector.riotredesign.core.utils.LiveEvent import im.vector.riotredesign.core.utils.LiveEvent
import im.vector.riotredesign.features.home.group.ALL_COMMUNITIES_GROUP_ID import im.vector.riotredesign.features.home.HomeRoomListObservableStore
import im.vector.riotredesign.features.home.group.SelectedGroupStore
import im.vector.riotredesign.features.home.room.VisibleRoomStore
import io.reactivex.Observable
import io.reactivex.functions.Function3
import org.koin.android.ext.android.get import org.koin.android.ext.android.get
import java.util.concurrent.TimeUnit
typealias RoomListFilterName = CharSequence typealias RoomListFilterName = CharSequence
class RoomListViewModel(initialState: RoomListViewState, class RoomListViewModel(initialState: RoomListViewState,
private val session: Session, private val session: Session,
private val selectedGroupHolder: SelectedGroupStore, private val homeRoomListObservableSource: HomeRoomListObservableStore,
private val visibleRoomHolder: VisibleRoomStore,
private val roomSelectionRepository: RoomSelectionRepository,
private val roomSummaryComparator: RoomSummaryComparator) private val roomSummaryComparator: RoomSummaryComparator)
: VectorViewModel<RoomListViewState>(initialState) { : VectorViewModel<RoomListViewState>(initialState) {
@ -53,11 +44,9 @@ class RoomListViewModel(initialState: RoomListViewState,
@JvmStatic @JvmStatic
override fun create(viewModelContext: ViewModelContext, state: RoomListViewState): RoomListViewModel? { override fun create(viewModelContext: ViewModelContext, state: RoomListViewState): RoomListViewModel? {
val currentSession = viewModelContext.activity.get<Session>() val currentSession = viewModelContext.activity.get<Session>()
val roomSelectionRepository = viewModelContext.activity.get<RoomSelectionRepository>() val homeRoomListObservableSource = viewModelContext.activity.get<HomeRoomListObservableStore>()
val selectedGroupHolder = viewModelContext.activity.get<SelectedGroupStore>()
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, homeRoomListObservableSource, roomSummaryComparator)
} }
} }
@ -70,7 +59,6 @@ class RoomListViewModel(initialState: RoomListViewState,
init { init {
observeRoomSummaries() observeRoomSummaries()
observeVisibleRoom()
} }
fun accept(action: RoomListActions) { fun accept(action: RoomListActions) {
@ -83,12 +71,9 @@ class RoomListViewModel(initialState: RoomListViewState,
// PRIVATE METHODS ***************************************************************************** // PRIVATE METHODS *****************************************************************************
private fun handleSelectRoom(action: RoomListActions.SelectRoom) = withState { state -> private fun handleSelectRoom(action: RoomListActions.SelectRoom) {
if (state.visibleRoomId != action.roomSummary.roomId) {
roomSelectionRepository.saveLastSelectedRoom(action.roomSummary.roomId)
_openRoomLiveData.postValue(LiveEvent(action.roomSummary.roomId)) _openRoomLiveData.postValue(LiveEvent(action.roomSummary.roomId))
} }
}
private fun handleFilterRooms(action: RoomListActions.FilterRooms) { private fun handleFilterRooms(action: RoomListActions.FilterRooms) {
val optionalFilter = Option.fromNullable(action.roomName) val optionalFilter = Option.fromNullable(action.roomName)
@ -99,44 +84,10 @@ class RoomListViewModel(initialState: RoomListViewState,
this.toggle(action.category) this.toggle(action.category)
} }
private fun observeVisibleRoom() {
visibleRoomHolder.observe()
.doOnNext {
setState { copy(visibleRoomId = it) }
}
.subscribe()
.disposeOnClear()
}
private fun observeRoomSummaries() { private fun observeRoomSummaries() {
Observable.combineLatest<List<RoomSummary>, Option<GroupSummary>, Option<RoomListFilterName>, RoomSummaries>( homeRoomListObservableSource.observe()
session.rx().liveRoomSummaries().throttleLast(300, TimeUnit.MILLISECONDS), .map { buildRoomSummaries(it) }
selectedGroupHolder.observe(),
roomListFilter.throttleLast(300, TimeUnit.MILLISECONDS),
Function3 { rooms, selectedGroupOption, filterRoomOption ->
val filteredRooms = filterRooms(rooms, filterRoomOption)
val selectedGroup = selectedGroupOption.orNull()
val filteredDirectRooms = filteredRooms
.filter { it.isDirect }
.filter {
if (selectedGroup == null || selectedGroup.groupId == ALL_COMMUNITIES_GROUP_ID) {
true
} else {
it.otherMemberIds
.intersect(selectedGroup.userIds)
.isNotEmpty()
}
}
val filteredGroupRooms = filteredRooms
.filter { !it.isDirect }
.filter {
selectedGroup?.groupId == ALL_COMMUNITIES_GROUP_ID
|| selectedGroup?.roomIds?.contains(it.roomId) ?: true
}
buildRoomSummaries(filteredDirectRooms + filteredGroupRooms)
}
)
.execute { async -> .execute { async ->
copy( copy(
asyncRooms = async asyncRooms = async
@ -144,17 +95,6 @@ class RoomListViewModel(initialState: RoomListViewState,
} }
} }
private fun filterRooms(rooms: List<RoomSummary>, filterRoomOption: Option<RoomListFilterName>): List<RoomSummary> {
val filterRoom = filterRoomOption.orNull()
return rooms.filter {
if (filterRoom.isNullOrBlank()) {
true
} else {
it.displayName.contains(other = filterRoom, ignoreCase = true)
}
}
}
private fun buildRoomSummaries(rooms: List<RoomSummary>): RoomSummaries { private fun buildRoomSummaries(rooms: List<RoomSummary>): RoomSummaries {
val invites = ArrayList<RoomSummary>() val invites = ArrayList<RoomSummary>()
val favourites = ArrayList<RoomSummary>() val favourites = ArrayList<RoomSummary>()

View file

@ -25,7 +25,6 @@ import im.vector.riotredesign.R
data class RoomListViewState( data class RoomListViewState(
val asyncRooms: Async<RoomSummaries> = Uninitialized, val asyncRooms: Async<RoomSummaries> = Uninitialized,
val visibleRoomId: String? = null,
val isInviteExpanded: Boolean = true, val isInviteExpanded: Boolean = true,
val isFavouriteRoomsExpanded: Boolean = true, val isFavouriteRoomsExpanded: Boolean = true,
val isDirectRoomsExpanded: Boolean = true, val isDirectRoomsExpanded: Boolean = true,

View file

@ -37,7 +37,7 @@ class RoomSummaryController(private val stringProvider: StringProvider
callback?.onToggleRoomCategory(category) callback?.onToggleRoomCategory(category)
} }
if (isExpanded) { if (isExpanded) {
buildRoomModels(summaries, viewState.visibleRoomId) buildRoomModels(summaries)
} }
} }
} }
@ -71,18 +71,16 @@ class RoomSummaryController(private val stringProvider: StringProvider
} }
} }
private fun buildRoomModels(summaries: List<RoomSummary>, selectedRoomId: String?) { private fun buildRoomModels(summaries: List<RoomSummary>) {
summaries.forEach { roomSummary -> summaries.forEach { roomSummary ->
val unreadCount = roomSummary.notificationCount val unreadCount = roomSummary.notificationCount
val showHighlighted = roomSummary.highlightCount > 0 val showHighlighted = roomSummary.highlightCount > 0
val isSelected = roomSummary.roomId == selectedRoomId
roomSummaryItem { roomSummaryItem {
id(roomSummary.roomId) id(roomSummary.roomId)
roomId(roomSummary.roomId) roomId(roomSummary.roomId)
roomName(roomSummary.displayName) roomName(roomSummary.displayName)
avatarUrl(roomSummary.avatarUrl) avatarUrl(roomSummary.avatarUrl)
selected(isSelected)
showHighlighted(showHighlighted) showHighlighted(showHighlighted)
unreadCount(unreadCount) unreadCount(unreadCount)
listener { callback?.onRoomSelected(roomSummary) } listener { callback?.onRoomSelected(roomSummary) }

View file

@ -16,6 +16,7 @@
package im.vector.riotredesign.features.home.room.list package im.vector.riotredesign.features.home.room.list
import android.view.ViewGroup
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.EpoxyAttribute
@ -23,7 +24,6 @@ import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder
import im.vector.riotredesign.core.epoxy.VectorEpoxyModel import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
import im.vector.riotredesign.core.platform.CheckableFrameLayout
import im.vector.riotredesign.features.home.AvatarRenderer import im.vector.riotredesign.features.home.AvatarRenderer
@ -33,7 +33,6 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
@EpoxyAttribute lateinit var roomName: CharSequence @EpoxyAttribute lateinit var roomName: CharSequence
@EpoxyAttribute lateinit var roomId: String @EpoxyAttribute lateinit var roomId: String
@EpoxyAttribute var avatarUrl: String? = null @EpoxyAttribute var avatarUrl: String? = null
@EpoxyAttribute var selected: Boolean = false
@EpoxyAttribute var unreadCount: Int = 0 @EpoxyAttribute var unreadCount: Int = 0
@EpoxyAttribute var showHighlighted: Boolean = false @EpoxyAttribute var showHighlighted: Boolean = false
@EpoxyAttribute var listener: (() -> Unit)? = null @EpoxyAttribute var listener: (() -> Unit)? = null
@ -42,7 +41,6 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
super.bind(holder) super.bind(holder)
holder.unreadCounterBadgeView.render(unreadCount, showHighlighted) holder.unreadCounterBadgeView.render(unreadCount, showHighlighted)
holder.rootView.isChecked = selected
holder.rootView.setOnClickListener { listener?.invoke() } holder.rootView.setOnClickListener { listener?.invoke() }
holder.titleView.text = roomName holder.titleView.text = roomName
AvatarRenderer.render(avatarUrl, roomId, roomName.toString(), holder.avatarImageView) AvatarRenderer.render(avatarUrl, roomId, roomName.toString(), holder.avatarImageView)
@ -52,7 +50,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
val unreadCounterBadgeView by bind<UnreadCounterBadgeView>(R.id.roomUnreadCounterBadgeView) val unreadCounterBadgeView by bind<UnreadCounterBadgeView>(R.id.roomUnreadCounterBadgeView)
val titleView by bind<TextView>(R.id.roomNameView) val titleView by bind<TextView>(R.id.roomNameView)
val avatarImageView by bind<ImageView>(R.id.roomAvatarImageView) val avatarImageView by bind<ImageView>(R.id.roomAvatarImageView)
val rootView by bind<CheckableFrameLayout>(R.id.itemRoomLayout) val rootView by bind<ViewGroup>(R.id.itemRoomLayout)
} }
} }

View file

@ -3,7 +3,7 @@
<item android:state_checked="true"> <item android:state_checked="true">
<shape> <shape>
<solid android:color="@android:color/white" /> <solid android:color="#10000000" />
<corners android:radius="4dp" /> <corners android:radius="4dp" />
</shape> </shape>
</item> </item>

View file

@ -2,9 +2,10 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/stateView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:clickable="true"
android:focusable="true">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/homeDrawerHeader" android:id="@+id/homeDrawerHeader"
@ -46,12 +47,16 @@
app:layout_constraintTop_toBottomOf="@+id/homeDrawerUsernameView" app:layout_constraintTop_toBottomOf="@+id/homeDrawerUsernameView"
tools:text="@tools:sample/full_names" /> tools:text="@tools:sample/full_names" />
<ImageButton <ImageView
android:id="@+id/homeDrawerHeaderSettingsView" android:id="@+id/homeDrawerHeaderSettingsView"
android:layout_width="wrap_content" android:layout_width="32dp"
android:layout_height="wrap_content" android:layout_height="32dp"
android:padding="6dp"
android:scaleType="fitCenter"
android:background="?attr/selectableItemBackground"
android:src="@drawable/ic_settings"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/homeDrawerUserIdView" /> app:layout_constraintTop_toBottomOf="@+id/homeDrawerUsernameView" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -6,7 +6,7 @@
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/roomToolbar"
style="@style/VectorToolbarStyle" style="@style/VectorToolbarStyle"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="?actionBarSize" android:layout_height="?actionBarSize"
@ -23,7 +23,7 @@
android:layout_height="match_parent"> android:layout_height="match_parent">
<ImageView <ImageView
android:id="@+id/toolbarAvatarImageView" android:id="@+id/roomToolbarAvatarImageView"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
@ -34,7 +34,7 @@
tools:src="@tools:sample/avatars" /> tools:src="@tools:sample/avatars" />
<TextView <TextView
android:id="@+id/toolbarTitleView" android:id="@+id/roomToolbarTitleView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
@ -43,15 +43,15 @@
android:maxLines="1" android:maxLines="1"
android:textColor="?vctr_toolbar_primary_text_color" android:textColor="?vctr_toolbar_primary_text_color"
android:textSize="18sp" android:textSize="18sp"
app:layout_constraintBottom_toTopOf="@+id/toolbarSubtitleView" app:layout_constraintBottom_toTopOf="@+id/roomToolbarSubtitleView"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/toolbarAvatarImageView" app:layout_constraintStart_toEndOf="@+id/roomToolbarAvatarImageView"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/full_names" /> tools:text="@tools:sample/full_names" />
<TextView <TextView
android:id="@+id/toolbarSubtitleView" android:id="@+id/roomToolbarSubtitleView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
@ -63,8 +63,8 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/toolbarAvatarImageView" app:layout_constraintStart_toEndOf="@+id/roomToolbarAvatarImageView"
app:layout_constraintTop_toBottomOf="@+id/toolbarTitleView" app:layout_constraintTop_toBottomOf="@+id/roomToolbarTitleView"
tools:text="@tools:sample/date/day_of_week" /> tools:text="@tools:sample/date/day_of_week" />
@ -79,7 +79,7 @@
app:layout_constraintBottom_toTopOf="@+id/composerDivider" app:layout_constraintBottom_toTopOf="@+id/composerDivider"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar" app:layout_constraintTop_toBottomOf="@id/roomToolbar"
tools:listitem="@layout/item_timeline_event_base" /> tools:listitem="@layout/item_timeline_event_base" />
<View <View
@ -148,7 +148,7 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar" app:layout_constraintTop_toBottomOf="@+id/roomToolbar"
app:layout_constraintVertical_bias="1.0" app:layout_constraintVertical_bias="1.0"
tools:visibility="visible" /> tools:visibility="visible" />

View file

@ -12,4 +12,11 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/createRoomButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp" />
</im.vector.riotredesign.core.platform.StateView> </im.vector.riotredesign.core.platform.StateView>

View file

@ -2,11 +2,12 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
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">
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/groupToolbar"
style="@style/VectorToolbarStyle" style="@style/VectorToolbarStyle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
@ -14,14 +15,42 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/> app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/groupToolbarAvatarImageView"
android:layout_width="40dp"
android:layout_height="40dp"
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/groupToolbarTitleView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?vctr_toolbar_primary_text_color"
android:textSize="18sp"
tools:text="@tools:sample/lorem/random" />
</LinearLayout>
</androidx.appcompat.widget.Toolbar>
<FrameLayout <FrameLayout
android:id="@+id/roomListContainer" android:id="@+id/roomListContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/bottomNavigationView" app:layout_constraintBottom_toTopOf="@+id/bottomNavigationView"
app:layout_constraintTop_toBottomOf="@+id/toolbar" /> app:layout_constraintTop_toBottomOf="@+id/groupToolbar" />
<com.google.android.material.bottomnavigation.BottomNavigationView <com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigationView" android:id="@+id/bottomNavigationView"

View file

@ -1,15 +1,25 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<im.vector.riotredesign.core.platform.CheckableFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/itemGroupLayout" android:id="@+id/itemGroupLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="56dp" android:layout_height="wrap_content"
android:background="@android:color/transparent" android:background="@drawable/bg_group_item"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:padding="16dp"> android:foreground="?attr/selectableItemBackground"
android:paddingStart="8dp"
android:paddingLeft="8dp"
android:paddingEnd="16dp"
android:paddingRight="16dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="56dp"
android:duplicateParentState="true">
<ImageView <ImageView
android:id="@+id/groupAvatarImageView" android:id="@+id/groupAvatarImageView"
@ -46,3 +56,5 @@
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</im.vector.riotredesign.core.platform.CheckableFrameLayout>

View file

@ -1,26 +1,20 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<im.vector.riotredesign.core.platform.CheckableFrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/itemRoomLayout" android:id="@+id/itemRoomLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/bg_room_item" android:background="?attr/selectableItemBackground"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:foreground="?attr/selectableItemBackground" android:minHeight="48dp"
android:paddingStart="8dp" android:paddingStart="8dp"
android:paddingLeft="8dp" android:paddingLeft="8dp"
android:paddingEnd="16dp" android:paddingEnd="16dp"
android:paddingRight="16dp"> android:paddingRight="16dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:duplicateParentState="true"
android:minHeight="48dp">
<ImageView <ImageView
android:id="@+id/roomAvatarImageView" android:id="@+id/roomAvatarImageView"
android:layout_width="32dp" android:layout_width="32dp"
@ -66,5 +60,3 @@
tools:text="115" /> tools:text="115" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</im.vector.riotredesign.core.platform.CheckableFrameLayout>

View file

@ -1,11 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/sliding_menu_settings"
android:icon="@drawable/ic_settings"
android:title="@string/room_sliding_menu_settings" />
<item <item
android:id="@+id/sliding_menu_sign_out" android:id="@+id/sliding_menu_sign_out"
android:icon="@drawable/ic_material_exit_to_app" android:icon="@drawable/ic_material_exit_to_app"