RoomDirectoryPicker WIP

This commit is contained in:
Benoit Marty 2019-05-24 15:00:43 +02:00
parent 877de1f597
commit 2404eeadf0
32 changed files with 1044 additions and 108 deletions

View File

@ -19,6 +19,7 @@ package im.vector.matrix.android.api.session.room
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsParams import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsParams
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsResponse import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsResponse
import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable


/** /**
@ -39,4 +40,10 @@ interface RoomDirectoryService {
fun joinRoom(roomId: String, fun joinRoom(roomId: String,
callback: MatrixCallback<Unit>) callback: MatrixCallback<Unit>)


/**
* Fetches the overall metadata about protocols supported by the homeserver.
* Includes both the available protocols and all fields required for queries against each protocol.
*/
fun getThirdPartyProtocol(callback: MatrixCallback<Map<String, ThirdPartyProtocol>>)

} }

View File

@ -46,12 +46,12 @@ data class PublicRoomsParams(
/** /**
* Whether or not to include all known networks/protocols from application services on the homeserver. Defaults to false. * Whether or not to include all known networks/protocols from application services on the homeserver. Defaults to false.
*/ */
@Json(name = "includeAllNetworks") @Json(name = "include_all_networks")
var includeAllNetworks: Boolean = false, var includeAllNetworks: Boolean = false,


/** /**
* The specific third party network/protocol to request from the homeserver. Can only be used if include_all_networks is false. * The specific third party network/protocol to request from the homeserver. Can only be used if include_all_networks is false.
*/ */
@Json(name = "thirdPartyInstanceId") @Json(name = "third_party_instance_id")
var thirdPartyInstanceId: String? = null var thirdPartyInstanceId: String? = null
) )

View File

@ -0,0 +1,36 @@
/*
* 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.matrix.android.api.session.room.model.thirdparty

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class FieldType(
/**
* Required. A regular expression for validation of a field's value. This may be relatively coarse to verify the value as the application
* service providing this protocol may apply additional
*/
@Json(name = "regexp")
val regexp: String? = null,

/**
* Required. An placeholder serving as a valid example of the field value.
*/
@Json(name = "placeholder")
val placeholder: String? = null
)

View File

@ -0,0 +1,55 @@
/*
* 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.matrix.android.api.session.room.model.thirdparty

/**
* This class describes a rooms directory server.
*/
data class RoomDirectoryData(

/**
* The server name (might be null)
* Set null when the server is the current user's home server.
*/
val homeServer: String? = null,

/**
* The display name (the server description)
*/
val displayName: String = DEFAULT_HOME_SERVER_NAME,

/**
* The third party server identifier
*/
val thirdPartyInstanceId: String? = null,

/**
* Tell if all the federated servers must be included
*/
val includeAllNetworks: Boolean = false,

/**
* the avatar url
*/
val avatarUrl: String? = null
) {

companion object {
const val DEFAULT_HOME_SERVER_NAME = "Matrix"
}

}

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.matrix.android.api.session.room.model.thirdparty

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class ThirdPartyProtocol(
/**
* Required. Fields which may be used to identify a third party user. These should be ordered to suggest the way that entities may be grouped,
* where higher groupings are ordered first. For example, the name of a network should be searched before the nickname of a user.
*/
@Json(name = "user_fields")
var userFields: List<String>? = null,

/**
* Required. Fields which may be used to identify a third party location. These should be ordered to suggest the way that
* entities may be grouped, where higher groupings are ordered first. For example, the name of a network should be
* searched before the name of a channel.
*/
@Json(name = "location_fields")
var locationFields: List<String>? = null,

/**
* Required. A content URI representing an icon for the third party protocol.
*
* FIXDOC: This field was not present in legacy Riot, and it is sometimes sent by the server (no not Required?)
*/
@Json(name = "icon")
var icon: String? = null,

/**
* Required. The type definitions for the fields defined in the user_fields and location_fields. Each entry in those arrays MUST have an entry here.
* The string key for this object is field name itself.
*
* May be an empty object if no fields are defined.
*/
@Json(name = "field_types")
var fieldTypes: Map<String, FieldType>? = null,

/**
* Required. A list of objects representing independent instances of configuration. For example, multiple networks on IRC
* if multiple are provided by the same application service.
*/
@Json(name = "instances")
var instances: List<ThirdPartyProtocolInstance>? = null
)

View File

@ -0,0 +1,60 @@
/*
* 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.matrix.android.api.session.room.model.thirdparty

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class ThirdPartyProtocolInstance(
/**
* Required. A human-readable description for the protocol, such as the name.
*/
@Json(name = "desc")
var desc: String? = null,

/**
* An optional content URI representing the protocol. Overrides the one provided at the higher level Protocol object.
*/
@Json(name = "icon")
var icon: String? = null,

/**
* Required. Preset values for fields the client may use to search by.
*/
@Json(name = "fields")
var fields: Map<String, Any>? = null,

/**
* Required. A unique identifier across all instances.
*/
@Json(name = "network_id")
var networkId: String? = null,

/**
* FIXDOC Not documented on matrix.org doc
*/
@Json(name = "instance_id")
var instanceId: String? = null,


/**
* FIXDOC Not documented on matrix.org doc
*/
@Json(name = "bot_user_id")
var botUserId: String? = null
)

View File

@ -36,6 +36,7 @@ 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.room.model.roomdirectory.PublicRoomsParams import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsParams
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsResponse import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsResponse
import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol
import im.vector.matrix.android.api.session.signout.SignOutService import im.vector.matrix.android.api.session.signout.SignOutService
import im.vector.matrix.android.api.session.sync.FilterService import im.vector.matrix.android.api.session.sync.FilterService
import im.vector.matrix.android.api.session.user.UserService import im.vector.matrix.android.api.session.user.UserService
@ -188,6 +189,11 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
return roomDirectoryService.joinRoom(roomId, callback) return roomDirectoryService.joinRoom(roomId, callback)
} }


override fun getThirdPartyProtocol(callback: MatrixCallback<Map<String, ThirdPartyProtocol>>) {
assert(isOpen)
return roomDirectoryService.getThirdPartyProtocol(callback)
}

// GROUP SERVICE // GROUP SERVICE


override fun getGroup(groupId: String): Group? { override fun getGroup(groupId: String): Group? {

View File

@ -36,7 +36,9 @@ import im.vector.matrix.android.internal.session.group.DefaultGroupService
import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater
import im.vector.matrix.android.internal.session.room.* import im.vector.matrix.android.internal.session.room.*
import im.vector.matrix.android.internal.session.room.directory.DefaultGetPublicRoomTask import im.vector.matrix.android.internal.session.room.directory.DefaultGetPublicRoomTask
import im.vector.matrix.android.internal.session.room.directory.DefaultGetThirdPartyProtocolsTask
import im.vector.matrix.android.internal.session.room.directory.GetPublicRoomTask import im.vector.matrix.android.internal.session.room.directory.GetPublicRoomTask
import im.vector.matrix.android.internal.session.room.directory.GetThirdPartyProtocolsTask
import im.vector.matrix.android.internal.session.room.membership.RoomDisplayNameResolver import im.vector.matrix.android.internal.session.room.membership.RoomDisplayNameResolver
import im.vector.matrix.android.internal.session.room.membership.RoomMemberDisplayNameResolver import im.vector.matrix.android.internal.session.room.membership.RoomMemberDisplayNameResolver
import im.vector.matrix.android.internal.session.room.prune.EventsPruner import im.vector.matrix.android.internal.session.room.prune.EventsPruner
@ -116,7 +118,11 @@ internal class SessionModule(private val sessionParams: SessionParams) {
} }


scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
DefaultRoomDirectoryService(get(), get(), get()) as RoomDirectoryService DefaultGetThirdPartyProtocolsTask(get()) as GetThirdPartyProtocolsTask
}

scope(DefaultSession.SCOPE) {
DefaultRoomDirectoryService(get(), get(), get(), get()) as RoomDirectoryService
} }


scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {

View File

@ -20,14 +20,17 @@ import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.room.RoomDirectoryService import im.vector.matrix.android.api.session.room.RoomDirectoryService
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsParams import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsParams
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsResponse import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsResponse
import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.session.room.directory.GetPublicRoomTask import im.vector.matrix.android.internal.session.room.directory.GetPublicRoomTask
import im.vector.matrix.android.internal.session.room.directory.GetThirdPartyProtocolsTask
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith


internal class DefaultRoomDirectoryService(private val getPublicRoomTask: GetPublicRoomTask, internal class DefaultRoomDirectoryService(private val getPublicRoomTask: GetPublicRoomTask,
private val joinRoomTask: JoinRoomTask, private val joinRoomTask: JoinRoomTask,
private val getThirdPartyProtocolsTask: GetThirdPartyProtocolsTask,
private val taskExecutor: TaskExecutor) : RoomDirectoryService { private val taskExecutor: TaskExecutor) : RoomDirectoryService {


override fun getPublicRooms(server: String?, override fun getPublicRooms(server: String?,
@ -45,4 +48,11 @@ internal class DefaultRoomDirectoryService(private val getPublicRoomTask: GetPub
.dispatchTo(callback) .dispatchTo(callback)
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }

override fun getThirdPartyProtocol(callback: MatrixCallback<Map<String, ThirdPartyProtocol>>) {
getThirdPartyProtocolsTask
.configureWith(Unit)
.dispatchTo(callback)
.executeBy(taskExecutor)
}
} }

View File

@ -25,6 +25,7 @@ import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRooms
import im.vector.matrix.android.internal.network.NetworkConstants import im.vector.matrix.android.internal.network.NetworkConstants
import im.vector.matrix.android.internal.session.room.membership.RoomMembersResponse import im.vector.matrix.android.internal.session.room.membership.RoomMembersResponse
import im.vector.matrix.android.internal.session.room.membership.joining.InviteBody import im.vector.matrix.android.internal.session.room.membership.joining.InviteBody
import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol
import im.vector.matrix.android.internal.session.room.send.SendResponse import im.vector.matrix.android.internal.session.room.send.SendResponse
import im.vector.matrix.android.internal.session.room.timeline.EventContextResponse import im.vector.matrix.android.internal.session.room.timeline.EventContextResponse
import im.vector.matrix.android.internal.session.room.timeline.PaginationResponse import im.vector.matrix.android.internal.session.room.timeline.PaginationResponse
@ -33,6 +34,14 @@ import retrofit2.http.*


internal interface RoomAPI { internal interface RoomAPI {


/**
* Get the third party server protocols.
*
* Ref: https://matrix.org/docs/spec/client_server/r0.4.0.html#get-matrix-client-r0-thirdparty-protocols
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "thirdparty/protocols")
fun thirdPartyProtocols(): Call<Map<String, ThirdPartyProtocol>>

/** /**
* Lists the public rooms on the server, with optional filter. * Lists the public rooms on the server, with optional filter.
* This API returns paginated responses. The rooms are ordered by the number of joined members, with the largest rooms first. * This API returns paginated responses. The rooms are ordered by the number of joined members, with the largest rooms first.

View File

@ -0,0 +1,34 @@
/*
* 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.matrix.android.internal.session.room.directory

import arrow.core.Try
import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.task.Task

internal interface GetThirdPartyProtocolsTask : Task<Unit, Map<String, ThirdPartyProtocol>>

internal class DefaultGetThirdPartyProtocolsTask(private val roomAPI: RoomAPI) : GetThirdPartyProtocolsTask {

override fun execute(params: Unit): Try<Map<String, ThirdPartyProtocol>> {
return executeRequest {
apiCall = roomAPI.thirdPartyProtocols()
}
}
}

View File

@ -20,6 +20,7 @@ import android.content.Context
import android.content.Context.MODE_PRIVATE import android.content.Context.MODE_PRIVATE
import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.Matrix
import im.vector.riotredesign.core.resources.LocaleProvider import im.vector.riotredesign.core.resources.LocaleProvider
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.VisibleRoomStore
@ -40,6 +41,10 @@ class AppModule(private val context: Context) {
StringProvider(context.resources) StringProvider(context.resources)
} }


single {
StringArrayProvider(context.resources)
}

single { single {
context.getSharedPreferences("im.vector.riot", MODE_PRIVATE) context.getSharedPreferences("im.vector.riot", MODE_PRIVATE)
} }

View File

@ -18,26 +18,26 @@ package im.vector.riotredesign.core.extensions


import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment


fun androidx.fragment.app.Fragment.addFragment(fragment: Fragment, frameId: Int) { fun Fragment.addFragment(fragment: Fragment, frameId: Int) {
fragmentManager?.inTransaction { add(frameId, fragment) } fragmentManager?.inTransaction { add(frameId, fragment) }
} }


fun androidx.fragment.app.Fragment.replaceFragment(fragment: Fragment, frameId: Int) { fun Fragment.replaceFragment(fragment: Fragment, frameId: Int) {
fragmentManager?.inTransaction { replace(frameId, fragment) } fragmentManager?.inTransaction { replace(frameId, fragment) }
} }


fun androidx.fragment.app.Fragment.addFragmentToBackstack(fragment: Fragment, frameId: Int, tag: String? = null) { fun Fragment.addFragmentToBackstack(fragment: Fragment, frameId: Int, tag: String? = null) {
fragmentManager?.inTransaction { replace(frameId, fragment).addToBackStack(tag) } fragmentManager?.inTransaction { replace(frameId, fragment).addToBackStack(tag) }
} }


fun androidx.fragment.app.Fragment.addChildFragment(fragment: Fragment, frameId: Int) { fun Fragment.addChildFragment(fragment: Fragment, frameId: Int) {
childFragmentManager.inTransaction { add(frameId, fragment) } childFragmentManager.inTransaction { add(frameId, fragment) }
} }


fun androidx.fragment.app.Fragment.replaceChildFragment(fragment: Fragment, frameId: Int) { fun Fragment.replaceChildFragment(fragment: Fragment, frameId: Int) {
childFragmentManager.inTransaction { replace(frameId, fragment) } childFragmentManager.inTransaction { replace(frameId, fragment) }
} }


fun androidx.fragment.app.Fragment.addChildFragmentToBackstack(fragment: Fragment, frameId: Int, tag: String? = null) { fun Fragment.addChildFragmentToBackstack(fragment: Fragment, frameId: Int, tag: String? = null) {
childFragmentManager.inTransaction { replace(frameId, fragment).addToBackStack(tag) } childFragmentManager.inTransaction { replace(frameId, fragment).addToBackStack(tag) }
} }

View File

@ -0,0 +1,33 @@
/*
* 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.core.extensions

import android.widget.TextView
import androidx.core.view.isVisible

/**
* Set a text in the TextView, or set visibility to GONE it if the text is null
*/
fun TextView.setTextOrHide(newText: String?, hideWhenBlank: Boolean = true) {
if (newText == null
|| (newText.isBlank() && hideWhenBlank)) {
isVisible = false
} else {
this.text = newText
isVisible = true
}
}

View File

@ -100,7 +100,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), OnBackPressed {


override fun invalidate() { override fun invalidate() {
//no-ops by default //no-ops by default
// TODO Remove default implementation? Timber.w("invalidate() method has not been implemented")
} }


protected fun setArguments(args: Parcelable? = null) { protected fun setArguments(args: Parcelable? = null) {

View File

@ -0,0 +1,38 @@
/*
* 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.core.resources

import android.content.res.Resources
import androidx.annotation.ArrayRes
import androidx.annotation.NonNull

class StringArrayProvider(private val resources: Resources) {

/**
* Returns a localized string array from the application's package's
* default string array table.
*
* @param resId Resource id for the string array
* @return The string array associated with the resource, stripped of styled
* text information.
*/
@NonNull
fun getStringArray(@ArrayRes resId: Int): Array<String> {
return resources.getStringArray(resId)
}

}

View File

@ -29,10 +29,9 @@ 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.features.home.AvatarRenderer import im.vector.riotredesign.features.home.AvatarRenderer


@EpoxyModelClass(layout = R.layout.item_room_directory) @EpoxyModelClass(layout = R.layout.item_public_room)
abstract class RoomDirectoryItem : VectorEpoxyModel<RoomDirectoryItem.Holder>() { abstract class PublicRoomItem : VectorEpoxyModel<PublicRoomItem.Holder>() {


// TODO Manage join state waiting from the sync
enum class JoinState { enum class JoinState {
NOT_JOINED, NOT_JOINED,
JOINING, JOINING,
@ -82,15 +81,15 @@ abstract class RoomDirectoryItem : VectorEpoxyModel<RoomDirectoryItem.Holder>()




class Holder : VectorEpoxyHolder() { class Holder : VectorEpoxyHolder() {
val rootView by bind<ViewGroup>(R.id.itemRoomDirectoryLayout) val rootView by bind<ViewGroup>(R.id.itemPublicRoomLayout)


val avatarView by bind<ImageView>(R.id.itemRoomDirectoryAvatar) val avatarView by bind<ImageView>(R.id.itemPublicRoomAvatar)
val nameView by bind<TextView>(R.id.itemRoomDirectoryName) val nameView by bind<TextView>(R.id.itemPublicRoomName)
val counterView by bind<TextView>(R.id.itemRoomDirectoryMembersCount) val counterView by bind<TextView>(R.id.itemPublicRoomMembersCount)


val joinedView by bind<View>(R.id.itemRoomDirectoryJoined) val joinedView by bind<View>(R.id.itemPublicRoomJoined)
val joinButton by bind<View>(R.id.itemRoomDirectoryJoin) val joinButton by bind<View>(R.id.itemPublicRoomJoin)
val joiningView by bind<View>(R.id.itemRoomDirectoryJoining) val joiningView by bind<View>(R.id.itemPublicRoomJoining)
} }


} }

View File

@ -28,11 +28,11 @@ import im.vector.riotredesign.core.epoxy.loadingItem
import im.vector.riotredesign.core.epoxy.noResultItem import im.vector.riotredesign.core.epoxy.noResultItem
import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.core.resources.StringProvider


class RoomDirectoryController(private val stringProvider: StringProvider) : TypedEpoxyController<RoomDirectoryViewState>() { class PublicRoomsController(private val stringProvider: StringProvider) : TypedEpoxyController<PublicRoomsViewState>() {


var callback: Callback? = null var callback: Callback? = null


override fun buildModels(viewState: RoomDirectoryViewState) { override fun buildModels(viewState: PublicRoomsViewState) {
val publicRooms = viewState.publicRooms val publicRooms = viewState.publicRooms


if (publicRooms.isEmpty() if (publicRooms.isEmpty()
@ -74,17 +74,17 @@ class RoomDirectoryController(private val stringProvider: StringProvider) : Type
} }
} }


private fun buildPublicRoom(publicRoom: PublicRoom, viewState: RoomDirectoryViewState) { private fun buildPublicRoom(publicRoom: PublicRoom, viewState: PublicRoomsViewState) {
roomDirectoryItem { publicRoomItem {
id(publicRoom.roomId) id(publicRoom.roomId)
roomId(publicRoom.roomId) roomId(publicRoom.roomId)
avatarUrl(publicRoom.avatarUrl) avatarUrl(publicRoom.avatarUrl)
roomName(publicRoom.name) roomName(publicRoom.name)
nbOfMembers(publicRoom.numJoinedMembers) nbOfMembers(publicRoom.numJoinedMembers)
when { when {
viewState.joinedRoomsIds.contains(publicRoom.roomId) -> joinState(RoomDirectoryItem.JoinState.JOINED) viewState.joinedRoomsIds.contains(publicRoom.roomId) -> joinState(PublicRoomItem.JoinState.JOINED)
viewState.joiningRoomsIds.contains(publicRoom.roomId) -> joinState(RoomDirectoryItem.JoinState.JOINING) viewState.joiningRoomsIds.contains(publicRoom.roomId) -> joinState(PublicRoomItem.JoinState.JOINING)
else -> joinState(RoomDirectoryItem.JoinState.NOT_JOINED) else -> joinState(PublicRoomItem.JoinState.NOT_JOINED)
} }
joinListener { joinListener {
callback?.onPublicRoomJoin(publicRoom) callback?.onPublicRoomJoin(publicRoom)

View File

@ -21,13 +21,15 @@ import android.text.Editable
import android.view.View import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.airbnb.epoxy.EpoxyVisibilityTracker import com.airbnb.epoxy.EpoxyVisibilityTracker
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.addFragmentToBackstack
import im.vector.riotredesign.core.platform.SimpleTextWatcher import im.vector.riotredesign.core.platform.SimpleTextWatcher
import im.vector.riotredesign.core.platform.VectorBaseFragment import im.vector.riotredesign.core.platform.VectorBaseFragment
import kotlinx.android.synthetic.main.fragment_room_directory.* import im.vector.riotredesign.features.roomdirectory.picker.RoomDirectoryPickerFragment
import kotlinx.android.synthetic.main.fragment_public_rooms.*
import org.koin.android.ext.android.get import org.koin.android.ext.android.get
import timber.log.Timber import timber.log.Timber


@ -38,19 +40,21 @@ import timber.log.Timber
* *
* FIXME Rotate screen launch again the request * FIXME Rotate screen launch again the request
* *
* For Nad: * TODO For Nad:
* Display number of rooms? * Display number of rooms?
* Picto size are not correct * Picto size are not correct
* Where I put the room directory picker? * Where I put the room directory picker?
* World Readable badge
* Guest can join badge
* *
*/ */
class RoomDirectoryFragment : VectorBaseFragment(), RoomDirectoryController.Callback { class PublicRoomsFragment : VectorBaseFragment(), PublicRoomsController.Callback {


private val viewModel: RoomDirectoryViewModel by fragmentViewModel() private val viewModel: RoomDirectoryViewModel by activityViewModel()


private val roomDirectoryController = RoomDirectoryController(get()) private val publicRoomsController = PublicRoomsController(get())


override fun getLayoutResId() = R.layout.fragment_room_directory override fun getLayoutResId() = R.layout.fragment_public_rooms


override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -62,16 +66,20 @@ class RoomDirectoryFragment : VectorBaseFragment(), RoomDirectoryController.Call
it.setDisplayHomeAsUpEnabled(true) it.setDisplayHomeAsUpEnabled(true)
} }


roomDirectoryFilter.addTextChangedListener(object : SimpleTextWatcher() { publicRoomsFilter.addTextChangedListener(object : SimpleTextWatcher() {
override fun afterTextChanged(s: Editable) { override fun afterTextChanged(s: Editable) {
// TODO Debounce // TODO Debounce
viewModel.filterWith(roomDirectoryFilter.text.toString()) viewModel.filterWith(publicRoomsFilter.text.toString())
} }
}) })


createNewRoom.setOnClickListener { publicRoomsCreateNewRoom.setOnClickListener {
vectorBaseActivity.notImplemented() vectorBaseActivity.notImplemented()
} }

publicRoomsChangeDirectory.setOnClickListener {
vectorBaseActivity.addFragmentToBackstack(RoomDirectoryPickerFragment(), R.id.simpleFragmentContainer)
}
} }


override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
@ -82,14 +90,14 @@ class RoomDirectoryFragment : VectorBaseFragment(), RoomDirectoryController.Call


private fun setupRecyclerView() { private fun setupRecyclerView() {
val epoxyVisibilityTracker = EpoxyVisibilityTracker() val epoxyVisibilityTracker = EpoxyVisibilityTracker()
epoxyVisibilityTracker.attach(roomDirectoryList) epoxyVisibilityTracker.attach(publicRoomsList)


val layoutManager = LinearLayoutManager(context) val layoutManager = LinearLayoutManager(context)


roomDirectoryList.layoutManager = layoutManager publicRoomsList.layoutManager = layoutManager
roomDirectoryController.callback = this publicRoomsController.callback = this


roomDirectoryList.setController(roomDirectoryController) publicRoomsList.setController(publicRoomsController)
} }


override fun onPublicRoomClicked(publicRoom: PublicRoom) { override fun onPublicRoomClicked(publicRoom: PublicRoom) {
@ -108,6 +116,9 @@ class RoomDirectoryFragment : VectorBaseFragment(), RoomDirectoryController.Call


override fun invalidate() = withState(viewModel) { state -> override fun invalidate() = withState(viewModel) { state ->
// Populate list with Epoxy // Populate list with Epoxy
roomDirectoryController.setData(state) publicRoomsController.setData(state)

// Directory name
publicRoomsDirectoryName.text = state.roomDirectoryDisplayName
} }
} }

View File

@ -24,6 +24,7 @@ import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsFilter import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsFilter
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsParams import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsParams
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsResponse import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsResponse
import im.vector.matrix.android.api.session.room.model.thirdparty.RoomDirectoryData
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
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
@ -32,13 +33,13 @@ import timber.log.Timber


private const val PUBLIC_ROOMS_LIMIT = 20 private const val PUBLIC_ROOMS_LIMIT = 20


class RoomDirectoryViewModel(initialState: RoomDirectoryViewState, class RoomDirectoryViewModel(initialState: PublicRoomsViewState,
private val session: Session) : VectorViewModel<RoomDirectoryViewState>(initialState) { private val session: Session) : VectorViewModel<PublicRoomsViewState>(initialState) {


companion object : MvRxViewModelFactory<RoomDirectoryViewModel, RoomDirectoryViewState> { companion object : MvRxViewModelFactory<RoomDirectoryViewModel, PublicRoomsViewState> {


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


return RoomDirectoryViewModel(state, currentSession) return RoomDirectoryViewModel(state, currentSession)
@ -52,10 +53,19 @@ class RoomDirectoryViewModel(initialState: RoomDirectoryViewState,


private var currentTask: Cancelable? = null private var currentTask: Cancelable? = null


// Default RoomDirectoryData
private var roomDirectoryData = RoomDirectoryData()

init { init {
// Load with empty filter // Load with empty filter
load() load()


setState {
copy(
roomDirectoryDisplayName = roomDirectoryData.displayName
)
}

// Observe joined room (from the sync) // Observe joined room (from the sync)
observeJoinedRooms() observeJoinedRooms()
} }
@ -84,11 +94,33 @@ class RoomDirectoryViewModel(initialState: RoomDirectoryViewState,
} }
} }


fun setRoomDirectoryData(roomDirectoryData: RoomDirectoryData) {
if (this.roomDirectoryData == roomDirectoryData) {
return
}

this.roomDirectoryData = roomDirectoryData

reset()

load()
}

fun filterWith(filter: String) { fun filterWith(filter: String) {
if (currentFilter == filter) {
return
}

currentTask?.cancel() currentTask?.cancel()


currentFilter = filter currentFilter = filter


reset()

load()
}

private fun reset() {
// Reset since token // Reset since token
since = null since = null


@ -96,10 +128,10 @@ class RoomDirectoryViewModel(initialState: RoomDirectoryViewState,
copy( copy(
publicRooms = emptyList(), publicRooms = emptyList(),
asyncPublicRoomsRequest = Loading(), asyncPublicRoomsRequest = Loading(),
hasMore = false) hasMore = false,
roomDirectoryDisplayName = roomDirectoryData.displayName
)
} }

load()
} }


fun loadMore() { fun loadMore() {
@ -115,13 +147,13 @@ class RoomDirectoryViewModel(initialState: RoomDirectoryViewState,
} }


private fun load() { private fun load() {
currentTask = session.getPublicRooms(null, // TODO session.sessionParams.homeServerConnectionConfig.homeServerUri.toString(), currentTask = session.getPublicRooms(roomDirectoryData.homeServer,
PublicRoomsParams( PublicRoomsParams(
limit = PUBLIC_ROOMS_LIMIT, limit = PUBLIC_ROOMS_LIMIT,
filter = PublicRoomsFilter(searchTerm = currentFilter), filter = PublicRoomsFilter(searchTerm = currentFilter),
includeAllNetworks = false, // TODO includeAllNetworks = roomDirectoryData.includeAllNetworks,
since = since, since = since,
thirdPartyInstanceId = null // TODO thirdPartyInstanceId = roomDirectoryData.thirdPartyInstanceId
), ),
object : MatrixCallback<PublicRoomsResponse> { object : MatrixCallback<PublicRoomsResponse> {
override fun onSuccess(data: PublicRoomsResponse) { override fun onSuccess(data: PublicRoomsResponse) {

View File

@ -21,7 +21,7 @@ import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom


data class RoomDirectoryViewState( data class PublicRoomsViewState(
// Store cumul of pagination result // Store cumul of pagination result
val publicRooms: List<PublicRoom> = emptyList(), val publicRooms: List<PublicRoom> = emptyList(),
// Current pagination request // Current pagination request
@ -31,5 +31,6 @@ data class RoomDirectoryViewState(
// List of roomIds that the user wants to join // List of roomIds that the user wants to join
val joiningRoomsIds: List<String> = emptyList(), val joiningRoomsIds: List<String> = emptyList(),
// List of joined roomId, // List of joined roomId,
val joinedRoomsIds: List<String> = emptyList() val joinedRoomsIds: List<String> = emptyList(),
val roomDirectoryDisplayName: String? = null
) : MvRxState ) : MvRxState

View File

@ -27,7 +27,7 @@ class RoomDirectoryActivity : VectorBaseActivity() {


override fun initUiAndData() { override fun initUiAndData() {
if (isFirstCreation()) { if (isFirstCreation()) {
addFragment(RoomDirectoryFragment(), R.id.simpleFragmentContainer) addFragment(PublicRoomsFragment(), R.id.simpleFragmentContainer)
} }
} }



View File

@ -0,0 +1,75 @@
/*
* 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.picker

import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder
import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
import im.vector.riotredesign.core.extensions.setTextOrHide
import im.vector.riotredesign.core.glide.GlideApp

@EpoxyModelClass(layout = R.layout.item_room_directory)
abstract class RoomDirectoryItem : VectorEpoxyModel<RoomDirectoryItem.Holder>() {

@EpoxyAttribute
var directoryAvatarUrl: String? = null

@EpoxyAttribute
var directoryName: String? = null

@EpoxyAttribute
var directoryDescription: String? = null

@EpoxyAttribute
var includeAllNetworks: Boolean = false

@EpoxyAttribute
var globalListener: (() -> Unit)? = null

override fun bind(holder: Holder) {
holder.rootView.setOnClickListener { globalListener?.invoke() }

// Avatar
GlideApp.with(holder.avatarView)
.load(directoryAvatarUrl)
.apply {
if (!includeAllNetworks) {
placeholder(R.drawable.network_matrix)
}
}
.into(holder.avatarView)

holder.nameView.text = directoryName
holder.descritionView.setTextOrHide(directoryDescription)
}


class Holder : VectorEpoxyHolder() {
val rootView by bind<ViewGroup>(R.id.itemRoomDirectoryLayout)

val avatarView by bind<ImageView>(R.id.itemRoomDirectoryAvatar)
val nameView by bind<TextView>(R.id.itemRoomDirectoryName)
val descritionView by bind<TextView>(R.id.itemRoomDirectoryDescription)
}

}

View File

@ -0,0 +1,139 @@
/*
* 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.picker

import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Incomplete
import com.airbnb.mvrx.Success
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.room.model.thirdparty.RoomDirectoryData
import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol
import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.errorWithRetryItem
import im.vector.riotredesign.core.epoxy.loadingItem
import im.vector.riotredesign.core.resources.StringArrayProvider
import im.vector.riotredesign.core.resources.StringProvider

class RoomDirectoryPickerController(private val stringProvider: StringProvider,
private val stringArrayProvider: StringArrayProvider,
private val credentials: Credentials
) : TypedEpoxyController<RoomDirectoryPickerViewState>() {

var callback: Callback? = null

var index = 0

override fun buildModels(viewState: RoomDirectoryPickerViewState) {
val asyncThirdPartyProtocol = viewState.asyncThirdPartyRequest

when (asyncThirdPartyProtocol) {
is Success -> {
val directories = computeDirectories(asyncThirdPartyProtocol.invoke())

directories.forEach {
buildDirectory(it)
}
}
is Incomplete -> {
loadingItem {
id("loading")
}
}
is Fail -> {
errorWithRetryItem {
id("error")
text(asyncThirdPartyProtocol.error.localizedMessage)
listener { callback?.retry() }
}
}
}
}

private fun computeDirectories(thirdPartyProtocolData: Map<String, ThirdPartyProtocol>): List<RoomDirectoryData> {
val result = ArrayList<RoomDirectoryData>()

// Add user homeserver name
val userHsName = credentials.userId.substring(credentials.userId.indexOf(":") + 1)

result.add(RoomDirectoryData(
displayName = userHsName,
includeAllNetworks = true
))

// Add user's HS but for Matrix public rooms only
result.add(RoomDirectoryData())

// Add custom directory servers
val hsNamesList = stringArrayProvider.getStringArray(R.array.room_directory_servers)
hsNamesList.forEach {
if (it != userHsName) {
// Use the server name as a default display name
result.add(RoomDirectoryData(
displayName = it,
includeAllNetworks = true
))
}
}

// Add result of the request
thirdPartyProtocolData.forEach {
it.value.instances?.forEach { thirdPartyProtocolInstance ->
result.add(RoomDirectoryData(
homeServer = null,
displayName = thirdPartyProtocolInstance.desc ?: "",
thirdPartyInstanceId = thirdPartyProtocolInstance.instanceId,
includeAllNetworks = false,
// Default to protocol icon
avatarUrl = thirdPartyProtocolInstance.icon ?: it.value.icon
))
}
}

return result
}

private fun buildDirectory(roomDirectoryData: RoomDirectoryData) {

// TODO
roomDirectoryItem {
id(index++)

directoryName(roomDirectoryData.displayName)

val description = when {
roomDirectoryData.includeAllNetworks -> stringProvider.getString(R.string.directory_server_all_rooms_on_server, roomDirectoryData.displayName)
"Matrix" == roomDirectoryData.displayName -> stringProvider.getString(R.string.directory_server_native_rooms, roomDirectoryData.displayName)
else -> null
}

directoryDescription(description)
directoryAvatarUrl(roomDirectoryData.avatarUrl)
includeAllNetworks(roomDirectoryData.includeAllNetworks)

globalListener {
callback?.onRoomDirectoryClicked(roomDirectoryData)
}
}
}

interface Callback {
fun onRoomDirectoryClicked(roomDirectory: RoomDirectoryData)
fun retry()
}

}

View File

@ -0,0 +1,103 @@
/*
* 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.picker

import android.os.Bundle
import android.view.MenuItem
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.room.model.thirdparty.RoomDirectoryData
import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.VectorBaseFragment
import im.vector.riotredesign.features.roomdirectory.RoomDirectoryViewModel
import kotlinx.android.synthetic.main.fragment_public_rooms.toolbar
import kotlinx.android.synthetic.main.fragment_room_directory_picker.*
import org.koin.android.ext.android.get
import timber.log.Timber

// TODO Set title to R.string.select_room_directory
// TODO Menu to add custom room directory (not done in RiotWeb so far...)
class RoomDirectoryPickerFragment : VectorBaseFragment(), RoomDirectoryPickerController.Callback {
private val viewModel: RoomDirectoryViewModel by activityViewModel()

private val pickerViewModel: RoomDirectoryPickerViewModel by fragmentViewModel()

private val roomDirectoryPickerController = RoomDirectoryPickerController(get(), get(), get<Session>().sessionParams.credentials)

override fun getLayoutResId() = R.layout.fragment_room_directory_picker

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

vectorBaseActivity.setSupportActionBar(toolbar)

vectorBaseActivity.supportActionBar?.let {
it.setDisplayShowHomeEnabled(true)
it.setDisplayHomeAsUpEnabled(true)
}
}

override fun getMenuRes() = R.menu.menu_directory_server_picker

override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.action_add_custom_hs) {
// TODO
vectorBaseActivity.notImplemented()
return true
}

return super.onOptionsItemSelected(item)
}

override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)

setupRecyclerView()
}

private fun setupRecyclerView() {
val layoutManager = LinearLayoutManager(context)

roomDirectoryPickerList.layoutManager = layoutManager
roomDirectoryPickerController.callback = this

roomDirectoryPickerList.setController(roomDirectoryPickerController)
}


override fun onRoomDirectoryClicked(roomDirectoryData: RoomDirectoryData) {
Timber.v("onRoomDirectoryClicked: $roomDirectoryData")
viewModel.setRoomDirectoryData(roomDirectoryData)

// TODO Not the bast way to manage Fragment Backstack...
vectorBaseActivity.onBackPressed()
}

override fun retry() {
Timber.v("Retry")
pickerViewModel.load()
}

override fun invalidate() = withState(pickerViewModel) { state ->
// Populate list with Epoxy
roomDirectoryPickerController.setData(state)
}
}

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.riotredesign.features.roomdirectory.picker

import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
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.thirdparty.ThirdPartyProtocol
import im.vector.riotredesign.core.platform.VectorViewModel
import org.koin.android.ext.android.get

class RoomDirectoryPickerViewModel(initialState: RoomDirectoryPickerViewState,
private val session: Session) : VectorViewModel<RoomDirectoryPickerViewState>(initialState) {

companion object : MvRxViewModelFactory<RoomDirectoryPickerViewModel, RoomDirectoryPickerViewState> {

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

return RoomDirectoryPickerViewModel(state, currentSession)
}
}

init {
load()
}

fun load() {
session.getThirdPartyProtocol(object : MatrixCallback<Map<String, ThirdPartyProtocol>> {
override fun onSuccess(data: Map<String, ThirdPartyProtocol>) {
setState {
copy(asyncThirdPartyRequest = Success(data))
}
}

override fun onFailure(failure: Throwable) {
setState {
copy(asyncThirdPartyRequest = Fail(failure))
}
}
})
}

}

View File

@ -0,0 +1,26 @@
/*
* 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.picker

import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol

data class RoomDirectoryPickerViewState(
val asyncThirdPartyRequest: Async<Map<String, ThirdPartyProtocol>> = Uninitialized
) : MvRxState

View File

@ -8,11 +8,11 @@
android:background="@color/pale_grey"> android:background="@color/pale_grey">


<com.airbnb.epoxy.EpoxyRecyclerView <com.airbnb.epoxy.EpoxyRecyclerView
android:id="@+id/roomDirectoryList" android:id="@+id/publicRoomsList"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_behavior="@string/appbar_scrolling_view_behavior" app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:listitem="@layout/item_room_directory" /> tools:listitem="@layout/item_public_room" />


<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -35,7 +35,7 @@
android:layout_height="wrap_content"> android:layout_height="wrap_content">


<EditText <EditText
android:id="@+id/roomDirectoryFilter" android:id="@+id/publicRoomsFilter"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/layout_horizontal_margin" android:layout_marginEnd="@dimen/layout_horizontal_margin"
@ -45,7 +45,7 @@
</androidx.appcompat.widget.Toolbar> </androidx.appcompat.widget.Toolbar>


<Button <Button
android:id="@+id/createNewRoom" android:id="@+id/publicRoomsCreateNewRoom"
style="@style/VectorButtonStyleFlat" style="@style/VectorButtonStyleFlat"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="48dp" android:layout_height="48dp"
@ -56,6 +56,26 @@
android:drawablePadding="13dp" android:drawablePadding="13dp"
android:text="@string/create_new_room" /> android:text="@string/create_new_room" />


<!-- TODO Layout -->
<Button
android:id="@+id/publicRoomsChangeDirectory"
style="@style/VectorButtonStyleFlat"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:text="RoomDirectory" />

<TextView
android:id="@+id/publicRoomsDirectoryName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginBottom="16dp"
android:textColor="#FFFFFF"
tools:text="RoomDirectoryName" />

</LinearLayout> </LinearLayout>


</com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.CollapsingToolbarLayout>

View File

@ -0,0 +1,35 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/pale_grey">

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">


<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<com.airbnb.epoxy.EpoxyRecyclerView
android:id="@+id/roomDirectoryPickerList"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
tools:listitem="@layout/item_room_directory" />

</androidx.constraintlayout.widget.ConstraintLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,105 @@
<?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:id="@+id/itemPublicRoomLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_room_item"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground">

<ImageView
android:id="@+id/itemPublicRoomAvatar"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
app:layout_constraintBottom_toTopOf="@+id/itemPublicRoomBottomSeparator"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />

<TextView
android:id="@+id/itemPublicRoomName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:maxLines="2"
android:textColor="#2E2F3E"
android:textSize="15sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/itemPublicRoomBottomSeparator"
app:layout_constraintEnd_toStartOf="@id/itemPublicRoomMembersCount"
app:layout_constraintStart_toEndOf="@id/itemPublicRoomAvatar"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/lorem/random" />

<TextView
android:id="@+id/itemPublicRoomMembersCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:drawableStart="@drawable/ic_user"
android:drawableLeft="@drawable/ic_user"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:minWidth="40dp"
android:textColor="#7E899C"
android:textSize="15sp"
app:layout_constraintBottom_toTopOf="@+id/itemPublicRoomBottomSeparator"
app:layout_constraintEnd_toStartOf="@id/itemPublicRoomJoin"
app:layout_constraintStart_toEndOf="@id/itemPublicRoomName"
app:layout_constraintTop_toTopOf="parent"
tools:text="148" />

<Button
android:id="@+id/itemPublicRoomJoin"
style="@style/VectorButtonStyleFlat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:text="@string/join"
app:layout_constraintBottom_toTopOf="@+id/itemPublicRoomBottomSeparator"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<ImageView
android:id="@+id/itemPublicRoomJoined"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="center"
android:src="@drawable/ic_tick"
app:layout_constraintBottom_toTopOf="@+id/itemPublicRoomBottomSeparator"
app:layout_constraintEnd_toEndOf="@+id/itemPublicRoomJoin"
app:layout_constraintStart_toStartOf="@+id/itemPublicRoomJoin"
app:layout_constraintTop_toTopOf="parent" />

<ProgressBar
android:id="@+id/itemPublicRoomJoining"
android:layout_width="0dp"
android:layout_height="24dp"
android:scaleType="center"
app:layout_constraintBottom_toTopOf="@+id/itemPublicRoomBottomSeparator"
app:layout_constraintEnd_toEndOf="@+id/itemPublicRoomJoin"
app:layout_constraintStart_toStartOf="@+id/itemPublicRoomJoin"
app:layout_constraintTop_toTopOf="parent" />

<View
android:id="@+id/itemPublicRoomBottomSeparator"
android:layout_width="0dp"
android:layout_height="1dp"
android:background="#E9EDF1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -31,67 +31,34 @@
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:ellipsize="end" android:ellipsize="end"
android:lines="1"
android:maxLines="2" android:maxLines="2"
android:textColor="#2E2F3E" android:textColor="#2E2F3E"
android:textSize="15sp" android:textSize="15sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/itemRoomDirectoryBottomSeparator" app:layout_constraintBottom_toTopOf="@+id/itemRoomDirectoryDescription"
app:layout_constraintEnd_toStartOf="@id/itemRoomDirectoryMembersCount" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/itemRoomDirectoryAvatar" app:layout_constraintStart_toEndOf="@id/itemRoomDirectoryAvatar"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="@tools:sample/lorem/random" /> tools:text="@tools:sample/lorem/random" />


<TextView <TextView
android:id="@+id/itemRoomDirectoryMembersCount" android:id="@+id/itemRoomDirectoryDescription"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="8dp" android:layout_marginStart="16dp"
android:layout_marginRight="8dp" android:layout_marginEnd="16dp"
android:drawableStart="@drawable/ic_user" android:ellipsize="end"
android:drawableLeft="@drawable/ic_user" android:lines="1"
android:drawablePadding="8dp" android:maxLines="2"
android:gravity="center_vertical" android:textColor="#2E2F3E"
android:minWidth="40dp"
android:textColor="#7E899C"
android:textSize="15sp" android:textSize="15sp"
app:layout_constraintBottom_toTopOf="@+id/itemRoomDirectoryBottomSeparator" app:layout_constraintBottom_toTopOf="@+id/itemRoomDirectoryBottomSeparator"
app:layout_constraintEnd_toStartOf="@id/itemRoomDirectoryJoin"
app:layout_constraintStart_toEndOf="@id/itemRoomDirectoryName"
app:layout_constraintTop_toTopOf="parent"
tools:text="148" />

<Button
android:id="@+id/itemRoomDirectoryJoin"
style="@style/VectorButtonStyleFlat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:text="@string/join"
app:layout_constraintBottom_toTopOf="@+id/itemRoomDirectoryBottomSeparator"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintStart_toEndOf="@id/itemRoomDirectoryAvatar"

app:layout_constraintTop_toBottomOf="@id/itemRoomDirectoryName"
<ImageView tools:text="@string/directory_server_all_rooms_on_server" />
android:id="@+id/itemRoomDirectoryJoined"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="center"
android:src="@drawable/ic_tick"
app:layout_constraintBottom_toTopOf="@+id/itemRoomDirectoryBottomSeparator"
app:layout_constraintEnd_toEndOf="@+id/itemRoomDirectoryJoin"
app:layout_constraintStart_toStartOf="@+id/itemRoomDirectoryJoin"
app:layout_constraintTop_toTopOf="parent" />

<ProgressBar
android:id="@+id/itemRoomDirectoryJoining"
android:layout_width="0dp"
android:layout_height="24dp"
android:scaleType="center"
app:layout_constraintBottom_toTopOf="@+id/itemRoomDirectoryBottomSeparator"
app:layout_constraintEnd_toEndOf="@+id/itemRoomDirectoryJoin"
app:layout_constraintStart_toStartOf="@+id/itemRoomDirectoryJoin"
app:layout_constraintTop_toTopOf="parent" />


<View <View
android:id="@+id/itemRoomDirectoryBottomSeparator" android:id="@+id/itemRoomDirectoryBottomSeparator"

View File

@ -4,7 +4,7 @@


<item <item
android:id="@+id/action_add_custom_hs" android:id="@+id/action_add_custom_hs"
android:icon="@drawable/ic_add_white" android:icon="@drawable/ic_add_black"
android:title="@string/action_open" android:title="@string/action_open"
app:showAsAction="always" /> app:showAsAction="always" />