From 8aab46804bbe5fb70fc0e156c10d7d368cc275f3 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 15 Nov 2019 20:37:36 +0100 Subject: [PATCH] Profile room: continue working on it (try to get a nice animation) [WIP] --- .../behavior/PercentViewBehavior.kt | 223 ++++++++++++++++++ .../core/epoxy/profiles/ProfileItemAction.kt | 4 + .../riotx/core/platform/VectorBaseActivity.kt | 10 +- .../roomprofile/RoomProfileController.kt | 50 ++-- .../roomprofile/RoomProfileFragment.kt | 19 +- .../roomprofile/RoomProfileViewModel.kt | 5 +- .../roomprofile/RoomProfileViewState.kt | 3 +- .../main/res/layout/fragment_room_profile.xml | 82 ++++--- .../main/res/layout/item_profile_action.xml | 8 +- vector/src/main/res/values/attr_behavior.xml | 23 ++ vector/src/main/res/values/strings_riotX.xml | 4 + 11 files changed, 374 insertions(+), 57 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/core/animations/behavior/PercentViewBehavior.kt create mode 100644 vector/src/main/res/values/attr_behavior.xml diff --git a/vector/src/main/java/im/vector/riotx/core/animations/behavior/PercentViewBehavior.kt b/vector/src/main/java/im/vector/riotx/core/animations/behavior/PercentViewBehavior.kt new file mode 100644 index 0000000000..d3ec1c1ed5 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/core/animations/behavior/PercentViewBehavior.kt @@ -0,0 +1,223 @@ +/* + * 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.animations.behavior + +import android.animation.ArgbEvaluator +import android.content.Context +import android.graphics.drawable.ColorDrawable +import android.util.AttributeSet +import android.view.View +import androidx.coordinatorlayout.widget.CoordinatorLayout + +import im.vector.riotx.R +import kotlin.math.abs + +private const val UNSPECIFIED_INT = Integer.MAX_VALUE +private val UNSPECIFIED_FLOAT = Float.MAX_VALUE +private const val DEPEND_TYPE_HEIGHT = 0 +private const val DEPEND_TYPE_WIDTH = 1 +private const val DEPEND_TYPE_X = 2 +private const val DEPEND_TYPE_Y = 3 + +class PercentViewBehavior(context: Context, attrs: AttributeSet) : CoordinatorLayout.Behavior(context, attrs) { + + private var dependType: Int = 0 + private var dependViewId: Int = 0 + private var dependTarget: Int = 0 + private var dependStartX: Int = 0 + private var dependStartY: Int = 0 + private var dependStartWidth: Int = 0 + private var dependStartHeight: Int = 0 + + private var startX: Int = 0 + private var startY: Int = 0 + private var startWidth: Int = 0 + private var startHeight: Int = 0 + private var startBackgroundColor: Int = 0 + private var startAlpha: Float = 0f + private var startRotateX: Float = 0f + private var startRotateY: Float = 0f + + private var targetX: Int = 0 + private var targetY: Int = 0 + private var targetWidth: Int = 0 + private var targetHeight: Int = 0 + private var targetBackgroundColor: Int = 0 + private var targetAlpha: Float = 0f + private var targetRotateX: Float = 0f + private var targetRotateY: Float = 0f + + /** + * Is the values prepared to be use + */ + private var isPrepared: Boolean = false + + init { + val a = context.obtainStyledAttributes(attrs, R.styleable.PercentViewBehavior) + dependViewId = a.getResourceId(R.styleable.PercentViewBehavior_behavior_dependsOn, 0) + dependType = a.getInt(R.styleable.PercentViewBehavior_behavior_dependType, DEPEND_TYPE_WIDTH) + dependTarget = a.getDimensionPixelOffset(R.styleable.PercentViewBehavior_behavior_dependTarget, UNSPECIFIED_INT) + targetX = a.getDimensionPixelOffset(R.styleable.PercentViewBehavior_behavior_targetX, UNSPECIFIED_INT) + targetY = a.getDimensionPixelOffset(R.styleable.PercentViewBehavior_behavior_targetY, UNSPECIFIED_INT) + targetWidth = a.getDimensionPixelOffset(R.styleable.PercentViewBehavior_behavior_targetWidth, UNSPECIFIED_INT) + targetHeight = a.getDimensionPixelOffset(R.styleable.PercentViewBehavior_behavior_targetHeight, UNSPECIFIED_INT) + targetBackgroundColor = a.getColor(R.styleable.PercentViewBehavior_behavior_targetBackgroundColor, UNSPECIFIED_INT) + targetAlpha = a.getFloat(R.styleable.PercentViewBehavior_behavior_targetAlpha, UNSPECIFIED_FLOAT) + targetRotateX = a.getFloat(R.styleable.PercentViewBehavior_behavior_targetRotateX, UNSPECIFIED_FLOAT) + targetRotateY = a.getFloat(R.styleable.PercentViewBehavior_behavior_targetRotateY, UNSPECIFIED_FLOAT) + a.recycle() + } + + private fun prepare(parent: CoordinatorLayout, child: View, dependency: View) { + dependStartX = dependency.x.toInt() + dependStartY = dependency.y.toInt() + dependStartWidth = dependency.width + dependStartHeight = dependency.height + startX = child.x.toInt() + startY = child.y.toInt() + startWidth = child.width + startHeight = child.height + startAlpha = child.alpha + startRotateX = child.rotationX + startRotateY = child.rotationY + + // only set the start background color when the background is color drawable + val background = child.background + if (background is ColorDrawable) { + startBackgroundColor = background.color + } + + // if parent fitsSystemWindows is true, add status bar height to target y if specified + if (parent.fitsSystemWindows && targetY != UNSPECIFIED_INT) { + var result = 0 + val resources = parent.context.resources + val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android") + if (resourceId > 0) { + result = resources.getDimensionPixelSize(resourceId) + } + targetY += result + } + isPrepared = true + } + + override fun layoutDependsOn(parent: CoordinatorLayout, child: V, dependency: View): Boolean { + return dependency.id == dependViewId + } + + override fun onDependentViewChanged(parent: CoordinatorLayout, child: V, dependency: View): Boolean { + // first time, prepare values before continue + if (!isPrepared) { + prepare(parent, child, dependency) + } + updateView(child, dependency) + return false + } + + override fun onLayoutChild(parent: CoordinatorLayout, child: V, layoutDirection: Int): Boolean { + val bool = super.onLayoutChild(parent, child, layoutDirection) + if (isPrepared) { + updateView(child, parent.getDependencies(child)[0]) + } + return bool + } + + /** + * Update the child view from the dependency states + * + * @param child child view + * @param dependency dependency view + */ + private fun updateView(child: V, dependency: View) { + var percent = 0f + var start = 0f + var current = 0f + var end = UNSPECIFIED_INT.toFloat() + when (dependType) { + DEPEND_TYPE_WIDTH -> { + start = dependStartWidth.toFloat() + current = dependency.width.toFloat() + end = dependTarget.toFloat() + } + DEPEND_TYPE_HEIGHT -> { + start = dependStartHeight.toFloat() + current = dependency.height.toFloat() + end = dependTarget.toFloat() + } + DEPEND_TYPE_X -> { + start = dependStartX.toFloat() + current = dependency.x + end = dependTarget.toFloat() + } + DEPEND_TYPE_Y -> { + start = dependStartY.toFloat() + current = dependency.y + end = dependTarget.toFloat() + } + } + + // need to define target value according to the depend type, if not then skip + if (end != UNSPECIFIED_INT.toFloat()) { + percent = abs(current - start) / abs(end - start) + } + updateViewWithPercent(child, if (percent > 1f) 1f else percent) + } + + private fun updateViewWithPercent(child: View, percent: Float) { + var newX = if (targetX == UNSPECIFIED_INT) 0f else (targetX - startX) * percent + var newY = if (targetY == UNSPECIFIED_INT) 0f else (targetY - startY) * percent + + // set scale + if (targetWidth != UNSPECIFIED_INT) { + val newWidth = startWidth + (targetWidth - startWidth) * percent + child.scaleX = newWidth / startWidth + newX -= (startWidth - newWidth) / 2 + } + if (targetHeight != UNSPECIFIED_INT) { + val newHeight = startHeight + (targetHeight - startHeight) * percent + child.scaleY = newHeight / startHeight + newY -= (startHeight - newHeight) / 2 + } + + // set new position + child.translationX = newX + child.translationY = newY + + // set alpha + if (targetAlpha != UNSPECIFIED_FLOAT) { + child.alpha = startAlpha + (targetAlpha - startAlpha) * percent + } + + // set background color + if (targetBackgroundColor != UNSPECIFIED_INT && startBackgroundColor != 0) { + val evaluator = ArgbEvaluator() + val color = evaluator.evaluate(percent, startBackgroundColor, targetBackgroundColor) as Int + child.setBackgroundColor(color) + } + + // set rotation + if (targetRotateX != UNSPECIFIED_FLOAT) { + child.rotationX = startRotateX + (targetRotateX - startRotateX) * percent + } + if (targetRotateY != UNSPECIFIED_FLOAT) { + child.rotationY = startRotateY + (targetRotateY - startRotateY) * percent + } + + child.requestLayout() + } + + +} diff --git a/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileItemAction.kt b/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileItemAction.kt index a69cf96243..eb34834f27 100644 --- a/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileItemAction.kt +++ b/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileItemAction.kt @@ -16,6 +16,7 @@ package im.vector.riotx.core.epoxy.profiles +import android.view.View import android.widget.ImageView import android.widget.TextView import androidx.core.view.isVisible @@ -37,9 +38,12 @@ abstract class ProfileItemAction : VectorEpoxyModel() var iconRes: Int = 0 @EpoxyAttribute var editable: Boolean = true + @EpoxyAttribute + lateinit var listener: View.OnClickListener override fun bind(holder: Holder) { super.bind(holder) + holder.view.setOnClickListener(listener) holder.editable.isVisible = editable holder.title.text = title holder.subtitle.setTextOrHide(subtitle) diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt index 4a3056657f..cca3505d1c 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt @@ -332,12 +332,10 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector { */ protected fun configureToolbar(toolbar: Toolbar, displayBack: Boolean = true) { setSupportActionBar(toolbar) - - if (displayBack) { - supportActionBar?.let { - it.setDisplayShowHomeEnabled(true) - it.setDisplayHomeAsUpEnabled(true) - } + supportActionBar?.let { + it.setDisplayShowHomeEnabled(displayBack) + it.setDisplayHomeAsUpEnabled(displayBack) + it.title = null } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileController.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileController.kt index a5f0682d11..f321d54274 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileController.kt @@ -22,29 +22,49 @@ 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 im.vector.riotx.core.resources.StringProvider import javax.inject.Inject -class RoomProfileController @Inject constructor() +class RoomProfileController @Inject constructor(private val stringProvider: StringProvider) : TypedEpoxyController() { + var callback: Callback? = null + + interface Callback { + fun onLearnMoreClicked() + fun onMemberListClicked() + fun onSettingsClicked() + } + override fun buildModels(data: RoomProfileViewState?) { if (data == null) { return } + val roomSummary = data.roomSummary() + profileItemSection { id("section_security") title("Security") } + + val learnMoreSubtitle = if (data.isEncrypted) { + R.string.room_profile_encrypted_subtitle + } else { + R.string.room_profile_not_encrypted_subtitle + } profileItemAction { id("action_learn_more") title("Learn more") editable(true) - subtitle("Messages in this room are not end-to-end encrypted.") + subtitle(stringProvider.getString(learnMoreSubtitle)) + listener { _ -> + callback?.onLearnMoreClicked() + } } - dividerItem{ + dividerItem { id("action_learn_more_divider") } @@ -53,33 +73,29 @@ class RoomProfileController @Inject constructor() title("Options") } + val numberOfMembers = (roomSummary?.otherMemberIds?.size ?: 0) + 1 profileItemAction { iconRes(R.drawable.ic_person_outline_black) id("action_member_list") - title("88 people") + title(stringProvider.getString(R.string.room_profile_member_list_title, numberOfMembers)) editable(true) + listener { _ -> + callback?.onMemberListClicked() + } } - dividerItem{ + 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) + iconRes(R.drawable.ic_room_actions_settings) id("action_settings") title("Room settings") editable(true) + listener { _ -> + callback?.onSettingsClicked() + } } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt index ff0b8be634..2523f8f6ff 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileFragment.kt @@ -43,7 +43,7 @@ class RoomProfileFragment @Inject constructor( private val roomProfileController: RoomProfileController, private val avatarRenderer: AvatarRenderer, val roomProfileViewModelFactory: RoomProfileViewModel.Factory -) : VectorBaseFragment() { +) : VectorBaseFragment(), RoomProfileController.Callback { private val roomProfileArgs: RoomProfileArgs by args() private val roomProfileViewModel: RoomProfileViewModel by fragmentViewModel() @@ -57,9 +57,11 @@ class RoomProfileFragment @Inject constructor( } private fun setupRecyclerView() { + roomProfileController.callback = this roomProfileRecyclerView.setHasFixedSize(true) roomProfileRecyclerView.layoutManager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false) roomProfileRecyclerView.adapter = roomProfileController.adapter + } override fun onDestroyView() { @@ -74,6 +76,7 @@ class RoomProfileFragment @Inject constructor( activity?.finish() } else { roomProfileNameView.text = it.displayName + roomProfileNameView2.text = it.displayName roomProfileIdView.text = it.roomId roomProfileTopicView.setTextOrHide(it.topic) avatarRenderer.render(it, roomProfileAvatarView) @@ -82,5 +85,19 @@ class RoomProfileFragment @Inject constructor( roomProfileController.setData(state) } + // RoomProfileController.Callback + + override fun onLearnMoreClicked() { + vectorBaseActivity.notImplemented() + } + + override fun onMemberListClicked() { + vectorBaseActivity.notImplemented("Room member list") + } + + override fun onSettingsClicked() { + vectorBaseActivity.notImplemented("Room settings") + } + } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt index d81c748bdd..52a48d101d 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewModel.kt @@ -55,7 +55,10 @@ class RoomProfileViewModel @AssistedInject constructor(@Assisted initialState: R room.rx().liveRoomSummary() .unwrap() .execute { - copy(roomSummary = it) + copy( + roomSummary = it, + isEncrypted = room.isEncrypted() + ) } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewState.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewState.kt index aed1488b07..7a5e74b10e 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/RoomProfileViewState.kt @@ -24,7 +24,8 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary data class RoomProfileViewState( val roomId: String, - val roomSummary: Async = Uninitialized + val roomSummary: Async = Uninitialized, + val isEncrypted: Boolean = false ) : MvRxState { constructor(args: RoomProfileArgs) : this(roomId = args.roomId) diff --git a/vector/src/main/res/layout/fragment_room_profile.xml b/vector/src/main/res/layout/fragment_room_profile.xml index 3e58c2eeb9..d7e3ca2555 100644 --- a/vector/src/main/res/layout/fragment_room_profile.xml +++ b/vector/src/main/res/layout/fragment_room_profile.xml @@ -7,49 +7,40 @@ android:background="?riotx_header_panel_background"> - - + app:layout_scrollFlags="scroll|exitUntilCollapsed|snap" + app:titleEnabled="false" + app:toolbarId="@+id/roomProfileToolbar"> - - + android:paddingTop="160dp" + android:minHeight="280dp" + app:layout_collapseMode="parallax" + app:layout_collapseParallaxMultiplier="0.9"> - - + + @@ -92,7 +85,42 @@ android:id="@+id/roomProfileRecyclerView" android:layout_width="match_parent" android:layout_height="match_parent" - app:layout_behavior="@string/appbar_scrolling_view_behavior" /> + app:layout_behavior="@string/appbar_scrolling_view_behavior" + tools:listitem="@layout/item_profile_action" /> + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_profile_action.xml b/vector/src/main/res/layout/item_profile_action.xml index 947d505f35..2d6e1d0612 100644 --- a/vector/src/main/res/layout/item_profile_action.xml +++ b/vector/src/main/res/layout/item_profile_action.xml @@ -25,8 +25,8 @@ 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" /> + android:visibility="gone" + app:layout_constraintTop_toTopOf="parent" /> + tools:text="Learn more" /> + tools:text="Messages in this room are not end-to-end encrypted" /> + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 6e5b5da92e..1cd27b39b4 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -19,4 +19,8 @@ Long click on a room to see more options + Messages in this room are not end-to-end encrypted. + Messages in this room are end-to-end encrypted. + "%1$d people" +