Join room from room preview

This commit is contained in:
Benoit Marty 2019-05-28 17:03:27 +02:00
parent 33fbcc8ba3
commit 6244913ab9
14 changed files with 277 additions and 32 deletions

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -30,13 +30,6 @@ import im.vector.riotredesign.features.home.AvatarRenderer
@EpoxyModelClass(layout = R.layout.item_public_room)
abstract class PublicRoomItem : VectorEpoxyModel<PublicRoomItem.Holder>() {

enum class JoinState {
NOT_JOINED,
JOINING,
JOINING_ERROR,
JOINED
}

@EpoxyAttribute
var avatarUrl: String? = null


View File

@ -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()
}

View File

@ -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()
}

View File

@ -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<ErrorFormatter>()
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")
}
}
}

View File

@ -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<RoomPreviewViewState>(initialState) {

companion object : MvRxViewModelFactory<RoomPreviewViewModel, RoomPreviewViewState> {

@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: RoomPreviewViewState): RoomPreviewViewModel? {
val currentSession = viewModelContext.activity.get<Session>()

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<Unit> {
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
)
}
}
})
}

}

View File

@ -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

View File

@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/roomPreviewNoPreviewRoot"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -64,15 +66,31 @@
android:textAppearance="@style/TextAppearance.Vector.Subtitle2"
android:textSize="14sp" />

<Button
android:id="@+id/roomPreviewNoPreviewJoin"
style="@style/VectorButtonStyle"
<TextView
android:id="@+id/roomPreviewNoPreviewError"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_vertical_margin"
android:gravity="center"
android:textColor="@color/vector_error_color"
android:textSize="15sp"
android:visibility="gone"
tools:text="Error"
tools:visibility="visible" />

<im.vector.riotredesign.core.platform.ButtonStateView
android:id="@+id/roomPreviewNoPreviewJoin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_vertical_margin"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="@dimen/layout_vertical_margin"
android:minWidth="120dp"
android:text="@string/join" />
app:bsv_button_text="@string/join"
app:bsv_loaded_image_src="@drawable/ic_tick"
app:bsv_use_flat_button="false"
app:layout_constraintBottom_toTopOf="@+id/itemPublicRoomBottomSeparator" />

</LinearLayout>


View File

@ -65,6 +65,7 @@
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
app:bsv_use_flat_button="true"
app:bsv_button_text="@string/join"
app:bsv_loaded_image_src="@drawable/ic_tick"
app:layout_constraintBottom_toTopOf="@+id/itemPublicRoomBottomSeparator"

View File

@ -7,7 +7,7 @@
tools:parentTag="android.widget.FrameLayout">

<Button
android:id="@+id/buttonStateButton"
android:id="@+id/buttonStateButtonFlat"
style="@style/VectorButtonStyleFlat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -17,6 +17,18 @@
app:layout_constraintTop_toTopOf="parent"
tools:text="@string/join" />

<Button
android:id="@+id/buttonStateButtonBig"
style="@style/VectorButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:minWidth="120dp"
app:layout_constraintBottom_toTopOf="@+id/itemPublicRoomBottomSeparator"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="@string/join" />

<ProgressBar
android:id="@+id/buttonStateLoading"
android:layout_width="32dp"

View File

@ -3,6 +3,7 @@

<declare-styleable name="ButtonStateView">

<attr name="bsv_use_flat_button" format="boolean" />
<attr name="bsv_loaded_image_src" format="reference" />
<attr name="bsv_button_text" format="reference|string" />