Room profile: start creating epoxy items and implementing UI

This commit is contained in:
ganfra 2019-11-14 20:13:59 +01:00
parent fa67509ac2
commit 0edc953a23
14 changed files with 428 additions and 27 deletions

View file

@ -12,17 +12,14 @@
* 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.riotx.core.epoxy.bottomsheet
package im.vector.riotx.core.epoxy
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
@EpoxyModelClass(layout = R.layout.item_bottom_sheet_divider)
abstract class BottomSheetItemSeparator : VectorEpoxyModel<BottomSheetItemSeparator.Holder>() {
@EpoxyModelClass(layout = R.layout.item_divider)
abstract class DividerItem : VectorEpoxyModel<DividerItem.Holder>() {
class Holder : VectorEpoxyHolder()
}

View file

@ -0,0 +1,62 @@
/*
* 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.riotx.core.epoxy.profiles
import android.widget.ImageView
import android.widget.TextView
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
import im.vector.riotx.core.extensions.setTextOrHide
@EpoxyModelClass(layout = R.layout.item_profile_action)
abstract class ProfileItemAction : VectorEpoxyModel<ProfileItemAction.Holder>() {
@EpoxyAttribute
lateinit var title: String
@EpoxyAttribute
var subtitle: String? = null
@EpoxyAttribute
var iconRes: Int = 0
@EpoxyAttribute
var editable: Boolean = true
override fun bind(holder: Holder) {
super.bind(holder)
holder.editable.isVisible = editable
holder.title.text = title
holder.subtitle.setTextOrHide(subtitle)
if (iconRes != 0) {
holder.icon.setImageResource(iconRes)
holder.icon.isVisible = true
} else {
holder.icon.isVisible = false
}
}
class Holder : VectorEpoxyHolder() {
val icon by bind<ImageView>(R.id.actionIcon)
val title by bind<TextView>(R.id.actionTitle)
val subtitle by bind<TextView>(R.id.actionSubtitle)
val editable by bind<ImageView>(R.id.actionEditable)
}
}

View file

@ -0,0 +1,45 @@
/*
* 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.riotx.core.epoxy.profiles
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel
@EpoxyModelClass(layout = R.layout.item_profile_section)
abstract class ProfileItemSection: VectorEpoxyModel<ProfileItemSection.Holder>() {
@EpoxyAttribute
lateinit var title: String
override fun bind(holder: Holder) {
super.bind(holder)
holder.sectionView.text = title
}
class Holder : VectorEpoxyHolder() {
val sectionView by bind<TextView>(R.id.itemProfileSectionView)
}
}

View file

@ -21,6 +21,7 @@ import com.airbnb.mvrx.Success
import im.vector.riotx.EmojiCompatFontProvider
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.bottomsheet.*
import im.vector.riotx.core.epoxy.dividerItem
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.home.AvatarRenderer
import javax.inject.Inject
@ -68,7 +69,7 @@ class MessageActionsEpoxyController @Inject constructor(private val stringProvid
// Quick reactions
if (state.canReact() && state.quickStates is Success) {
// Separator
bottomSheetItemSeparator {
dividerItem {
id("reaction_separator")
}
@ -86,14 +87,14 @@ class MessageActionsEpoxyController @Inject constructor(private val stringProvid
}
// Separator
bottomSheetItemSeparator {
dividerItem {
id("actions_separator")
}
// Action
state.actions()?.forEachIndexed { index, action ->
if (action is EventSharedAction.Separator) {
bottomSheetItemSeparator {
dividerItem {
id("separator_$index")
}
} else {

View file

@ -20,7 +20,7 @@ import com.airbnb.epoxy.TypedEpoxyController
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetItemAction
import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetItemRoomPreview
import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetItemSeparator
import im.vector.riotx.core.epoxy.dividerItem
import im.vector.riotx.features.home.AvatarRenderer
import javax.inject.Inject
@ -46,7 +46,7 @@ class RoomListQuickActionsEpoxyController @Inject constructor(private val avatar
}
// Notifications
bottomSheetItemSeparator {
dividerItem {
id("notifications_separator")
}
@ -57,7 +57,7 @@ class RoomListQuickActionsEpoxyController @Inject constructor(private val avatar
RoomListQuickActionsSharedAction.NotificationsMute(roomSummary.roomId).toBottomSheetItem(3, selectedRoomState)
// Leave
bottomSheetItemSeparator {
dividerItem {
id("leave_separator")
}
RoomListQuickActionsSharedAction.Leave(roomSummary.roomId).toBottomSheetItem(5)

View file

@ -19,11 +19,13 @@ package im.vector.riotx.features.roomprofile
import android.content.Context
import android.content.Intent
import androidx.appcompat.widget.Toolbar
import im.vector.riotx.R
import im.vector.riotx.core.extensions.addFragment
import im.vector.riotx.core.platform.ToolbarConfigurable
import im.vector.riotx.core.platform.VectorBaseActivity
class RoomProfileActivity : VectorBaseActivity() {
class RoomProfileActivity : VectorBaseActivity(), ToolbarConfigurable {
companion object {
@ -47,5 +49,8 @@ class RoomProfileActivity : VectorBaseActivity() {
}
}
override fun configure(toolbar: Toolbar) {
configureToolbar(toolbar)
}
}

View file

@ -18,6 +18,10 @@
package im.vector.riotx.features.roomprofile
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.dividerItem
import im.vector.riotx.core.epoxy.profiles.profileItemAction
import im.vector.riotx.core.epoxy.profiles.profileItemSection
import javax.inject.Inject
class RoomProfileController @Inject constructor()
@ -27,5 +31,56 @@ class RoomProfileController @Inject constructor()
if (data == null) {
return
}
profileItemSection {
id("section_security")
title("Security")
}
profileItemAction {
id("action_learn_more")
title("Learn more")
editable(true)
subtitle("Messages in this room are not end-to-end encrypted.")
}
dividerItem{
id("action_learn_more_divider")
}
profileItemSection {
id("section_options")
title("Options")
}
profileItemAction {
iconRes(R.drawable.ic_person_outline_black)
id("action_member_list")
title("88 people")
editable(true)
}
dividerItem{
id("action_member_list_divider")
}
profileItemAction {
iconRes(R.drawable.ic_attachment)
id("action_files")
title("12 files")
editable(true)
}
dividerItem{
id("action_files_divider")
}
profileItemAction {
iconRes(R.drawable.ic_settings_x)
id("action_settings")
title("Room settings")
editable(true)
}
}
}

View file

@ -20,13 +20,18 @@ package im.vector.riotx.features.roomprofile
import android.os.Bundle
import android.os.Parcelable
import android.view.View
import com.airbnb.epoxy.EpoxyController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.riotx.R
import im.vector.riotx.core.extensions.setTextOrHide
import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.features.home.AvatarRenderer
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_room_profile.*
import timber.log.Timber
import javax.inject.Inject
@Parcelize
@ -36,6 +41,7 @@ data class RoomProfileArgs(
class RoomProfileFragment @Inject constructor(
private val roomProfileController: RoomProfileController,
private val avatarRenderer: AvatarRenderer,
val roomProfileViewModelFactory: RoomProfileViewModel.Factory
) : VectorBaseFragment() {
@ -46,10 +52,34 @@ class RoomProfileFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupToolbar(roomProfileToolbar)
setupRecyclerView()
}
override fun invalidate() = withState(roomProfileViewModel) {
roomProfileController.setData(it)
private fun setupRecyclerView() {
roomProfileRecyclerView.setHasFixedSize(true)
roomProfileRecyclerView.layoutManager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
roomProfileRecyclerView.adapter = roomProfileController.adapter
}
override fun onDestroyView() {
super.onDestroyView()
roomProfileRecyclerView.adapter = null
}
override fun invalidate() = withState(roomProfileViewModel) { state ->
state.roomSummary()?.let {
if (it.membership.isLeft()) {
Timber.w("The room has been left")
activity?.finish()
} else {
roomProfileNameView.text = it.displayName
roomProfileIdView.text = it.roomId
roomProfileTopicView.setTextOrHide(it.topic)
avatarRenderer.render(it, roomProfileAvatarView)
}
}
roomProfileController.setData(state)
}

View file

@ -22,9 +22,13 @@ import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.rx.rx
import im.vector.matrix.rx.unwrap
import im.vector.riotx.core.platform.VectorViewModel
class RoomProfileViewModel @AssistedInject constructor(@Assisted initialState: RoomProfileViewState)
class RoomProfileViewModel @AssistedInject constructor(@Assisted initialState: RoomProfileViewState,
private val session: Session)
: VectorViewModel<RoomProfileViewState, RoomProfileAction>(initialState) {
@AssistedInject.Factory
@ -41,6 +45,20 @@ class RoomProfileViewModel @AssistedInject constructor(@Assisted initialState: R
}
}
private val room = session.getRoom(initialState.roomId)!!
init {
observeRoomSummary()
}
private fun observeRoomSummary() {
room.rx().liveRoomSummary()
.unwrap()
.execute {
copy(roomSummary = it)
}
}
override fun handle(action: RoomProfileAction) {
}

View file

@ -17,10 +17,14 @@
package im.vector.riotx.features.roomprofile
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.room.model.RoomSummary
data class RoomProfileViewState(
val roomId: String
val roomId: String,
val roomSummary: Async<RoomSummary> = Uninitialized
) : MvRxState {
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)

View file

@ -1,14 +1,98 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:background="?riotx_header_panel_background">
<TextView
android:id="@+id/roomProfileTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="ROOM PROFILE" />
<com.google.android.material.appbar.AppBarLayout
style="@style/VectorAppBarLayoutStyle"
android:id="@+id/roomProfileAppBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:titleEnabled="false">
<androidx.appcompat.widget.Toolbar
android:id="@+id/roomProfileToolbar"
style="@style/VectorToolbarStyle"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?riotx_background"
android:orientation="vertical"
app:layout_collapseMode="parallax">
<ImageView
android:id="@+id/roomProfileAvatarView"
android:layout_width="128dp"
android:layout_height="128dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
android:src="@drawable/ic_person_outline_black" />
<TextView
android:id="@+id/roomProfileNameView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
android:singleLine="true"
android:textAppearance="@style/Vector.Toolbar.Title"
android:textStyle="bold"
android:textSize="20sp"
tools:text="Random" />
<TextView
android:id="@+id/roomProfileIdView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
android:singleLine="true"
android:textAppearance="@style/Vector.Toolbar.Title"
android:textSize="16sp"
tools:text="#random:matrix.org" />
<TextView
android:id="@+id/roomProfileTopicView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginStart="40dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="40dp"
android:fontFamily="sans-serif"
android:gravity="center"
android:textSize="14sp"
android:textStyle="normal"
tools:text="Here is a room topic, it can be multi-line but should always be displayed in full 🍱" />
<Space
android:layout_width="match_parent"
android:layout_height="24dp"/>
</LinearLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</FrameLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/roomProfileRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
android:background="?riotx_background"
android:minHeight="64dp"
android:paddingLeft="@dimen/layout_horizontal_margin"
android:paddingTop="8dp"
android:paddingRight="@dimen/layout_horizontal_margin"
android:paddingBottom="8dp">
<ImageView
android:id="@+id/actionIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:scaleType="center"
android:tint="?riotx_text_secondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_room_actions_notifications_all" />
<TextView
android:id="@+id/actionTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:drawablePadding="16dp"
android:ellipsize="end"
android:maxLines="2"
android:textColor="?riotx_text_primary"
android:textSize="16sp"
app:layout_constraintStart_toEndOf="@id/actionIcon"
app:layout_constraintEnd_toStartOf="@+id/actionEditable"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintBottom_toTopOf="@+id/actionSubtitle"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_default="wrap"
tools:text="zbla azjazj" />
<TextView
android:id="@+id/actionSubtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:drawablePadding="16dp"
android:ellipsize="end"
android:maxLines="2"
android:textColor="?riotx_text_secondary"
android:textSize="12sp"
app:layout_constraintStart_toEndOf="@id/actionIcon"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/actionEditable"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintTop_toBottomOf="@id/actionTitle"
app:layout_constraintWidth_default="wrap"
tools:text="zbla azjazj sdqkqsdkqsd sdqkqsdkqsdk kqdkqsdqk" />
<ImageView
android:id="@+id/actionEditable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_arrow_right"
android:visibility="gone"
android:tint="?riotx_text_secondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/itemProfileSectionView"
android:layout_width="match_parent"
tools:text="Security"
android:gravity="bottom"
android:paddingBottom="8dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="?riotx_text_primary"
android:background="?riotx_header_panel_background"
android:layout_height="68dp"/>