From 6244913ab9e86fa0bc39aeb1e788b7ea38229a98 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 28 May 2019 17:03:27 +0200 Subject: [PATCH] Join room from room preview --- .../riotredesign/core/error/ErrorFormatter.kt | 3 +- .../core/platform/ButtonStateView.kt | 26 ++-- .../features/roomdirectory/JoinState.kt | 28 +++++ .../features/roomdirectory/PublicRoomItem.kt | 7 -- .../roomdirectory/PublicRoomsController.kt | 10 +- .../roomdirectory/PublicRoomsFragment.kt | 10 +- ...ViewModel.kt => RoomDirectoryViewModel.kt} | 0 .../RoomPreviewNoPreviewFragment.kt | 43 ++++++- .../roompreview/RoomPreviewViewModel.kt | 111 ++++++++++++++++++ .../roompreview/RoomPreviewViewState.kt | 29 +++++ .../fragment_room_preview_no_preview.xml | 26 +++- .../src/main/res/layout/item_public_room.xml | 1 + .../src/main/res/layout/view_button_state.xml | 14 ++- .../main/res/values/attrs_state_button.xml | 1 + 14 files changed, 277 insertions(+), 32 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotredesign/features/roomdirectory/JoinState.kt rename vector/src/main/java/im/vector/riotredesign/features/roomdirectory/{PublicRoomsViewModel.kt => RoomDirectoryViewModel.kt} (100%) create mode 100644 vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewViewModel.kt create mode 100644 vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewViewState.kt diff --git a/vector/src/main/java/im/vector/riotredesign/core/error/ErrorFormatter.kt b/vector/src/main/java/im/vector/riotredesign/core/error/ErrorFormatter.kt index af676c10fd..114e08b866 100644 --- a/vector/src/main/java/im/vector/riotredesign/core/error/ErrorFormatter.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/error/ErrorFormatter.kt @@ -28,9 +28,10 @@ class ErrorFormatter(val stringProvider: StringProvider) { return failure.localizedMessage } - fun toHumanReadable(throwable: Throwable): String { + fun toHumanReadable(throwable: Throwable?): String { return when (throwable) { + null -> "" is Failure.NetworkConnection -> stringProvider.getString(R.string.error_no_network) else -> throwable.localizedMessage } diff --git a/vector/src/main/java/im/vector/riotredesign/core/platform/ButtonStateView.kt b/vector/src/main/java/im/vector/riotredesign/core/platform/ButtonStateView.kt index 151d34c05a..f9aaf0d05d 100755 --- a/vector/src/main/java/im/vector/riotredesign/core/platform/ButtonStateView.kt +++ b/vector/src/main/java/im/vector/riotredesign/core/platform/ButtonStateView.kt @@ -20,6 +20,7 @@ import android.content.Context import android.util.AttributeSet import android.view.View import android.view.ViewGroup +import android.widget.Button import android.widget.FrameLayout import androidx.core.view.isInvisible import androidx.core.view.isVisible @@ -43,14 +44,13 @@ class ButtonStateView @JvmOverloads constructor(context: Context, attrs: Attribu fun onRetryClicked() } + // Big or Flat button + var button: Button + init { View.inflate(context, R.layout.view_button_state, this) layoutParams = LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) - buttonStateButton.setOnClickListener { - callback?.onButtonClicked() - } - buttonStateRetry.setOnClickListener { callback?.onRetryClicked() } @@ -62,20 +62,32 @@ class ButtonStateView @JvmOverloads constructor(context: Context, attrs: Attribu 0, 0) .apply { try { - buttonStateButton.text = getString(R.styleable.ButtonStateView_bsv_button_text) + if (getBoolean(R.styleable.ButtonStateView_bsv_use_flat_button, true)) { + button = buttonStateButtonFlat + buttonStateButtonBig.isVisible = false + } else { + button = buttonStateButtonBig + buttonStateButtonFlat.isVisible = false + } + + button.text = getString(R.styleable.ButtonStateView_bsv_button_text) buttonStateLoaded.setImageDrawable(getDrawable(R.styleable.ButtonStateView_bsv_loaded_image_src)) } finally { recycle() } } + + button.setOnClickListener { + callback?.onButtonClicked() + } } fun render(newState: State) { if (newState == State.Button) { - buttonStateButton.isVisible = true + button.isVisible = true } else { // We use isInvisible because we want to keep button space in the layout - buttonStateButton.isInvisible = true + button.isInvisible = true } buttonStateLoading.isVisible = newState == State.Loading diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/JoinState.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/JoinState.kt new file mode 100644 index 0000000000..a521218119 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/JoinState.kt @@ -0,0 +1,28 @@ +/* + * 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.roomdirectory + +/** + * Join state of a room + */ +enum class JoinState { + NOT_JOINED, + JOINING, + JOINING_ERROR, + // Room is joined and this is confirmed by the sync + JOINED +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomItem.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomItem.kt index 6487b39c9d..2825c09f91 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomItem.kt @@ -30,13 +30,6 @@ import im.vector.riotredesign.features.home.AvatarRenderer @EpoxyModelClass(layout = R.layout.item_public_room) abstract class PublicRoomItem : VectorEpoxyModel() { - enum class JoinState { - NOT_JOINED, - JOINING, - JOINING_ERROR, - JOINED - } - @EpoxyAttribute var avatarUrl: String? = null diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsController.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsController.kt index 6f3725f110..50b1d3cd39 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsController.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsController.kt @@ -85,10 +85,10 @@ class PublicRoomsController(private val stringProvider: StringProvider, nbOfMembers(publicRoom.numJoinedMembers) val joinState = when { - viewState.joinedRoomsIds.contains(publicRoom.roomId) -> PublicRoomItem.JoinState.JOINED - viewState.joiningRoomsIds.contains(publicRoom.roomId) -> PublicRoomItem.JoinState.JOINING - viewState.joiningErrorRoomsIds.contains(publicRoom.roomId) -> PublicRoomItem.JoinState.JOINING_ERROR - else -> PublicRoomItem.JoinState.NOT_JOINED + viewState.joinedRoomsIds.contains(publicRoom.roomId) -> JoinState.JOINED + viewState.joiningRoomsIds.contains(publicRoom.roomId) -> JoinState.JOINING + viewState.joiningErrorRoomsIds.contains(publicRoom.roomId) -> JoinState.JOINING_ERROR + else -> JoinState.NOT_JOINED } joinState(joinState) @@ -103,7 +103,7 @@ class PublicRoomsController(private val stringProvider: StringProvider, } interface Callback { - fun onPublicRoomClicked(publicRoom: PublicRoom, joinState: PublicRoomItem.JoinState) + fun onPublicRoomClicked(publicRoom: PublicRoom, joinState: JoinState) fun onPublicRoomJoin(publicRoom: PublicRoom) fun loadMore() } diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsFragment.kt index 630064a769..d92af320c8 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsFragment.kt @@ -117,21 +117,21 @@ class PublicRoomsFragment : VectorBaseFragment(), PublicRoomsController.Callback publicRoomsList.setController(publicRoomsController) } - override fun onPublicRoomClicked(publicRoom: PublicRoom, joinState: PublicRoomItem.JoinState) { + override fun onPublicRoomClicked(publicRoom: PublicRoom, joinState: JoinState) { Timber.v("PublicRoomClicked: $publicRoom") when (joinState) { - PublicRoomItem.JoinState.JOINED -> { + JoinState.JOINED -> { val args = RoomDetailArgs(publicRoom.roomId) val roomDetailIntent = RoomDetailActivity.newIntent(requireActivity(), args) requireActivity().startActivity(roomDetailIntent) } - PublicRoomItem.JoinState.NOT_JOINED, - PublicRoomItem.JoinState.JOINING_ERROR -> { + JoinState.NOT_JOINED, + JoinState.JOINING_ERROR -> { // ROOM PREVIEW requireActivity().startActivity(RoomPreviewActivity.getIntent(requireActivity(), publicRoom)) } - else -> { + else -> { Snackbar.make(publicRoomsCoordinator, getString(R.string.please_wait), Snackbar.LENGTH_SHORT) .show() } diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/RoomDirectoryViewModel.kt similarity index 100% rename from vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsViewModel.kt rename to vector/src/main/java/im/vector/riotredesign/features/roomdirectory/RoomDirectoryViewModel.kt diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt index 5b6b902f92..bc119dcae1 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt @@ -19,13 +19,20 @@ package im.vector.riotredesign.features.roomdirectory.roompreview import android.os.Bundle import android.view.View import androidx.fragment.app.Fragment +import androidx.transition.TransitionManager import com.airbnb.mvrx.args +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState import im.vector.riotredesign.R +import im.vector.riotredesign.core.error.ErrorFormatter import im.vector.riotredesign.core.extensions.setTextOrHide +import im.vector.riotredesign.core.platform.ButtonStateView import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.features.home.AvatarRenderer +import im.vector.riotredesign.features.roomdirectory.JoinState import im.vector.riotredesign.features.roomdirectory.RoomDirectoryModule import kotlinx.android.synthetic.main.fragment_room_preview_no_preview.* +import org.koin.android.ext.android.get import org.koin.android.scope.ext.android.bindScope import org.koin.android.scope.ext.android.getOrCreateScope @@ -37,12 +44,17 @@ class RoomPreviewNoPreviewFragment : VectorBaseFragment() { } } + private val errorFormatter = get() + private val roomPreviewViewModel: RoomPreviewViewModel by fragmentViewModel() + private val roomPreviewData: RoomPreviewData by args() override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) bindScope(getOrCreateScope(RoomDirectoryModule.ROOM_DIRECTORY_SCOPE)) setupToolbar(roomPreviewNoPreviewToolbar) + + roomPreviewViewModel.init(roomPreviewData.roomId) } override fun getLayoutResId() = R.layout.fragment_room_preview_no_preview @@ -54,8 +66,35 @@ class RoomPreviewNoPreviewFragment : VectorBaseFragment() { roomPreviewNoPreviewName.text = roomPreviewData.roomName roomPreviewNoPreviewTopic.setTextOrHide(roomPreviewData.topic) - roomPreviewNoPreviewJoin.setOnClickListener { - vectorBaseActivity.notImplemented("Join from preview") + roomPreviewNoPreviewJoin.callback = object : ButtonStateView.Callback { + override fun onButtonClicked() { + roomPreviewViewModel.joinRoom() + } + + override fun onRetryClicked() { + // Same action + onButtonClicked() + } + } + } + + override fun invalidate() = withState(roomPreviewViewModel) { state -> + TransitionManager.beginDelayedTransition(roomPreviewNoPreviewRoot) + + roomPreviewNoPreviewJoin.render( + when (state.roomJoinState) { + JoinState.NOT_JOINED -> ButtonStateView.State.Button + JoinState.JOINING -> ButtonStateView.State.Loading + JoinState.JOINED -> ButtonStateView.State.Loaded + JoinState.JOINING_ERROR -> ButtonStateView.State.Error + } + ) + + roomPreviewNoPreviewError.setTextOrHide(errorFormatter.toHumanReadable(state.lastError)) + + if (state.roomJoinState == JoinState.JOINED) { + // TODO Quit this screen and open the room + vectorBaseActivity.notImplemented("Open newly join room") } } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewViewModel.kt new file mode 100644 index 0000000000..e3e0c01925 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewViewModel.kt @@ -0,0 +1,111 @@ +/* + * 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.roomdirectory.roompreview + +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.ViewModelContext +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.room.model.Membership +import im.vector.matrix.rx.rx +import im.vector.riotredesign.core.platform.VectorViewModel +import im.vector.riotredesign.features.roomdirectory.JoinState +import org.koin.android.ext.android.get +import timber.log.Timber + +class RoomPreviewViewModel(initialState: RoomPreviewViewState, + private val session: Session) : VectorViewModel(initialState) { + + companion object : MvRxViewModelFactory { + + @JvmStatic + override fun create(viewModelContext: ViewModelContext, state: RoomPreviewViewState): RoomPreviewViewModel? { + val currentSession = viewModelContext.activity.get() + + return RoomPreviewViewModel(state, currentSession) + } + } + + init { + // Observe joined room (from the sync) + observeJoinedRooms() + } + + private fun observeJoinedRooms() { + session + .rx() + .liveRoomSummaries() + .execute { async -> + val isRoomJoined = async.invoke() + // Keep only joined room + ?.filter { it.membership == Membership.JOIN } + ?.map { it.roomId } + ?.toList() + ?.contains(roomId) == true + + if (isRoomJoined) { + copy( + roomJoinState = JoinState.JOINED + ) + } else { + // TODO No change... + copy() + } + } + } + + // TODO I should not have to do that + fun init(roomId: String) = withState { + setState { + copy( + roomId = roomId + ) + } + } + + fun joinRoom() = withState { state -> + if (state.roomJoinState == JoinState.JOINING) { + // Request already sent, should not happen + Timber.w("Try to join an already joining room. Should not happen") + return@withState + } + + setState { + copy( + roomJoinState = JoinState.JOINING, + lastError = null + ) + } + + session.joinRoom(state.roomId, object : MatrixCallback { + override fun onSuccess(data: Unit) { + // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. + // Instead, we wait for the room to be joined + } + + override fun onFailure(failure: Throwable) { + setState { + copy( + roomJoinState = JoinState.JOINING_ERROR, + lastError = failure + ) + } + } + }) + } + +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewViewState.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewViewState.kt new file mode 100644 index 0000000000..20e23dd0e7 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewViewState.kt @@ -0,0 +1,29 @@ +/* + * 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.roomdirectory.roompreview + +import com.airbnb.mvrx.MvRxState +import im.vector.riotredesign.features.roomdirectory.JoinState + +data class RoomPreviewViewState( + // The room id + val roomId: String = "", + // Current state of the room in preview + val roomJoinState: JoinState = JoinState.NOT_JOINED, + // Last error of join room request + val lastError: Throwable? = null +) : MvRxState \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_room_preview_no_preview.xml b/vector/src/main/res/layout/fragment_room_preview_no_preview.xml index 30a378ed9a..7ec6b4813b 100644 --- a/vector/src/main/res/layout/fragment_room_preview_no_preview.xml +++ b/vector/src/main/res/layout/fragment_room_preview_no_preview.xml @@ -1,5 +1,7 @@ @@ -64,15 +66,31 @@ android:textAppearance="@style/TextAppearance.Vector.Subtitle2" android:textSize="14sp" /> -