mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-26 19:36:08 +03:00
Basic peeking preview and join and filter
This commit is contained in:
parent
c5fa0a413f
commit
df341d8ea3
42 changed files with 1604 additions and 31 deletions
|
@ -52,6 +52,7 @@ object EventType {
|
|||
const val STATE_ROOM_GUEST_ACCESS = "m.room.guest_access"
|
||||
const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels"
|
||||
const val STATE_SPACE_CHILD = "m.space.child"
|
||||
// const val STATE_SPACE_CHILD = "org.matrix.msc1772.space"
|
||||
|
||||
/**
|
||||
* Note that this Event has been deprecated, see
|
||||
|
|
|
@ -34,4 +34,6 @@ sealed class PeekResult {
|
|||
) : PeekResult()
|
||||
|
||||
object UnknownAlias : PeekResult()
|
||||
|
||||
fun isSuccess() = this is Success
|
||||
}
|
||||
|
|
|
@ -23,4 +23,6 @@ interface Space {
|
|||
fun asRoom() : Room
|
||||
|
||||
suspend fun addRoom(roomId: String)
|
||||
|
||||
// fun getChildren() : List<IRoomSummary>
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.space
|
|||
|
||||
import androidx.lifecycle.LiveData
|
||||
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
||||
import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult
|
||||
|
||||
typealias SpaceSummaryQueryParams = RoomSummaryQueryParams
|
||||
|
||||
|
@ -35,6 +36,13 @@ interface SpaceService {
|
|||
*/
|
||||
fun getSpace(spaceId: String): Space?
|
||||
|
||||
/**
|
||||
* Try to resolve (peek) rooms and subspace in this space.
|
||||
* Use this call get preview of children of this space, particularly useful to get a
|
||||
* preview of rooms that you did not join yet.
|
||||
*/
|
||||
suspend fun peekSpace(spaceId: String) : SpacePeekResult
|
||||
|
||||
/**
|
||||
* Get a live list of space summaries. This list is refreshed as soon as the data changes.
|
||||
* @return the [LiveData] of List[SpaceSummary]
|
||||
|
@ -42,4 +50,23 @@ interface SpaceService {
|
|||
fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData<List<SpaceSummary>>
|
||||
|
||||
fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List<SpaceSummary>
|
||||
|
||||
data class ChildAutoJoinInfo(
|
||||
val roomIdOrAlias: String,
|
||||
val viaServers: List<String> = emptyList()
|
||||
)
|
||||
|
||||
sealed class JoinSpaceResult {
|
||||
object Success: JoinSpaceResult()
|
||||
data class Fail(val error: Throwable?): JoinSpaceResult()
|
||||
/** Success fully joined the space, but failed to join all or some of it's rooms */
|
||||
data class PartialSuccess(val failedRooms: Map<String, Throwable>) : JoinSpaceResult()
|
||||
|
||||
fun isSuccess() = this is Success || this is PartialSuccess
|
||||
}
|
||||
|
||||
suspend fun joinSpace(spaceIdOrAlias: String,
|
||||
reason: String? = null,
|
||||
viaServers: List<String> = emptyList(),
|
||||
autoJoinChild: List<ChildAutoJoinInfo>) : JoinSpaceResult
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ data class SpaceChildContent(
|
|||
@Json(name = "via") val via: List<String>? = null,
|
||||
/**
|
||||
* present: true key is included to distinguish from a deleted state event
|
||||
* Children where present is not present or is not set to true are ignored.
|
||||
*/
|
||||
@Json(name = "present") val present: Boolean? = false,
|
||||
/**
|
||||
|
|
|
@ -91,6 +91,8 @@ import org.matrix.android.sdk.internal.session.room.typing.SendTypingTask
|
|||
import org.matrix.android.sdk.internal.session.room.uploads.DefaultGetUploadsTask
|
||||
import org.matrix.android.sdk.internal.session.room.uploads.GetUploadsTask
|
||||
import org.matrix.android.sdk.internal.session.space.DefaultSpaceService
|
||||
import org.matrix.android.sdk.internal.session.space.peeking.DefaultPeekSpaceTask
|
||||
import org.matrix.android.sdk.internal.session.space.peeking.PeekSpaceTask
|
||||
import retrofit2.Retrofit
|
||||
|
||||
@Module
|
||||
|
@ -236,6 +238,9 @@ internal abstract class RoomModule {
|
|||
@Binds
|
||||
abstract fun bindPeekRoomTask(task: DefaultPeekRoomTask): PeekRoomTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindPeekSpaceTask(task: DefaultPeekSpaceTask): PeekSpaceTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindGetEventTask(task: DefaultGetEventTask): GetEventTask
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ internal class RoomRelationshipHelper(private val realm: Realm,
|
|||
.filter { ContentMapper.map(it.root?.content).toModel<SpaceChildContent>()?.present == true }
|
||||
.mapNotNull {
|
||||
// ContentMapper.map(it.root?.content).toModel<SpaceChildContent>()
|
||||
it.roomId
|
||||
it.stateKey
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -167,7 +167,7 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||
spaceSummaryEntity.children.clear()
|
||||
spaceSummaryEntity.children.addAll(
|
||||
RoomRelationshipHelper(realm, roomId).getDirectChildrenDescriptions()
|
||||
.map { RoomSummaryEntity.getOrCreate(realm, roomId) }
|
||||
.map { RoomSummaryEntity.getOrCreate(realm, it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,4 +35,12 @@ class DefaultSpace(private val room: Room) : Space {
|
|||
body = SpaceChildContent(present = true).toContent()
|
||||
)
|
||||
}
|
||||
|
||||
// override fun getChildren(): List<IRoomSummary> {
|
||||
// // asRoom().getStateEvents(setOf(EventType.STATE_SPACE_CHILD)).mapNotNull {
|
||||
// // // statekeys are the roomIds
|
||||
// //
|
||||
// // }
|
||||
// return emptyList<IRoomSummary>()
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -32,6 +32,8 @@ import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask
|
|||
import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
|
||||
import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask
|
||||
import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask
|
||||
import org.matrix.android.sdk.internal.session.space.peeking.PeekSpaceTask
|
||||
import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult
|
||||
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask
|
||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
import javax.inject.Inject
|
||||
|
@ -46,6 +48,7 @@ internal class DefaultSpaceService @Inject constructor(
|
|||
private val deleteRoomAliasTask: DeleteRoomAliasTask,
|
||||
private val roomGetter: RoomGetter,
|
||||
private val spaceSummaryDataSource: SpaceSummaryDataSource,
|
||||
private val peekSpaceTask: PeekSpaceTask,
|
||||
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
|
||||
private val taskExecutor: TaskExecutor
|
||||
) : SpaceService {
|
||||
|
@ -67,4 +70,31 @@ internal class DefaultSpaceService @Inject constructor(
|
|||
override fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List<SpaceSummary> {
|
||||
return spaceSummaryDataSource.getSpaceSummaries(spaceSummaryQueryParams)
|
||||
}
|
||||
|
||||
override suspend fun peekSpace(spaceId: String): SpacePeekResult {
|
||||
return peekSpaceTask.execute(PeekSpaceTask.Params(spaceId))
|
||||
}
|
||||
|
||||
override suspend fun joinSpace(spaceIdOrAlias: String, reason: String?, viaServers: List<String>, autoJoinChild: List<SpaceService.ChildAutoJoinInfo>): SpaceService.JoinSpaceResult {
|
||||
try {
|
||||
joinRoomTask.execute(JoinRoomTask.Params(spaceIdOrAlias, reason, viaServers))
|
||||
val childJoinFailures = mutableMapOf<String, Throwable>()
|
||||
autoJoinChild.forEach { info ->
|
||||
// TODO what if the child is it self a subspace with some default children?
|
||||
try {
|
||||
joinRoomTask.execute(JoinRoomTask.Params(info.roomIdOrAlias, null, info.viaServers))
|
||||
} catch (failure: Throwable) {
|
||||
// TODO, i could already be a member of this room, handle that as it should not be an error in this context
|
||||
childJoinFailures[info.roomIdOrAlias] = failure
|
||||
}
|
||||
}
|
||||
return if (childJoinFailures.isEmpty()) {
|
||||
SpaceService.JoinSpaceResult.Success
|
||||
} else {
|
||||
SpaceService.JoinSpaceResult.PartialSuccess(childJoinFailures)
|
||||
}
|
||||
} catch (throwable: Throwable) {
|
||||
return SpaceService.JoinSpaceResult.Fail(throwable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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 org.matrix.android.sdk.internal.session.space.peeking
|
||||
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
|
||||
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
|
||||
import org.matrix.android.sdk.api.session.space.model.SpaceChildContent
|
||||
import org.matrix.android.sdk.internal.session.room.peeking.PeekRoomTask
|
||||
import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface PeekSpaceTask : Task<PeekSpaceTask.Params, SpacePeekResult> {
|
||||
data class Params(
|
||||
val roomIdOrAlias: String,
|
||||
// A depth limit as a simple protection against cycles
|
||||
val maxDepth: Int = 4
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultPeekSpaceTask @Inject constructor(
|
||||
private val peekRoomTask: PeekRoomTask,
|
||||
private val resolveRoomStateTask: ResolveRoomStateTask
|
||||
) : PeekSpaceTask {
|
||||
|
||||
override suspend fun execute(params: PeekSpaceTask.Params): SpacePeekResult {
|
||||
val peekResult = peekRoomTask.execute(PeekRoomTask.Params(params.roomIdOrAlias))
|
||||
val roomResult = peekResult as? PeekResult.Success ?: return SpacePeekResult.FailedToResolve(params.roomIdOrAlias, peekResult)
|
||||
|
||||
// check the room type
|
||||
// kind of duplicate cause we already did it in Peek? could we pass on the result??
|
||||
val stateEvents = try {
|
||||
resolveRoomStateTask.execute(ResolveRoomStateTask.Params(roomResult.roomId))
|
||||
} catch (failure: Throwable) {
|
||||
return SpacePeekResult.FailedToResolve(params.roomIdOrAlias, peekResult)
|
||||
}
|
||||
val isSpace = stateEvents
|
||||
.lastOrNull { it.type == EventType.STATE_ROOM_CREATE && it.stateKey == "" }
|
||||
?.let { it.content?.toModel<RoomCreateContent>()?.type } == RoomType.SPACE
|
||||
|
||||
if (!isSpace) return SpacePeekResult.NotSpaceType(params.roomIdOrAlias)
|
||||
|
||||
val children = peekChildren(stateEvents, 0, params.maxDepth)
|
||||
|
||||
return SpacePeekResult.Success(
|
||||
SpacePeekSummary(
|
||||
params.roomIdOrAlias,
|
||||
peekResult,
|
||||
children
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun peekChildren(stateEvents: List<Event>, depth: Int, maxDepth: Int): List<ISpaceChild> {
|
||||
if (depth >= maxDepth) return emptyList()
|
||||
val childRoomsIds = stateEvents
|
||||
.filter {
|
||||
it.type == EventType.STATE_SPACE_CHILD && !it.stateKey.isNullOrEmpty()
|
||||
// Children where present is not present or is not set to true are ignored.
|
||||
&& it.content?.toModel<SpaceChildContent>()?.present == true
|
||||
}
|
||||
.map { it.stateKey to it.content?.toModel<SpaceChildContent>() }
|
||||
|
||||
Timber.v("## SPACE_PEEK: found ${childRoomsIds.size} present children")
|
||||
|
||||
val spaceChildResults = mutableListOf<ISpaceChild>()
|
||||
childRoomsIds.forEach { entry ->
|
||||
|
||||
Timber.v("## SPACE_PEEK: peeking child $entry")
|
||||
// peek each child
|
||||
val childId = entry.first ?: return@forEach
|
||||
try {
|
||||
val childPeek = peekRoomTask.execute(PeekRoomTask.Params(childId))
|
||||
|
||||
val childStateEvents = resolveRoomStateTask.execute(ResolveRoomStateTask.Params(childId))
|
||||
val createContent = childStateEvents
|
||||
.lastOrNull { it.type == EventType.STATE_ROOM_CREATE && it.stateKey == "" }
|
||||
?.let { it.content?.toModel<RoomCreateContent>() }
|
||||
|
||||
if (!childPeek.isSuccess() || createContent == null) {
|
||||
Timber.v("## SPACE_PEEK: cannot peek child $entry")
|
||||
// can't peek :/
|
||||
spaceChildResults.add(
|
||||
SpaceChildPeekResult(
|
||||
childId, childPeek, entry.second?.default, entry.second?.order
|
||||
)
|
||||
)
|
||||
// continue to next child
|
||||
return@forEach
|
||||
}
|
||||
val type = createContent.type
|
||||
if (type == RoomType.SPACE) {
|
||||
Timber.v("## SPACE_PEEK: subspace child $entry")
|
||||
spaceChildResults.add(
|
||||
SpaceSubChildPeekResult(
|
||||
childId,
|
||||
childPeek,
|
||||
entry.second?.default,
|
||||
entry.second?.order,
|
||||
peekChildren(childStateEvents, depth + 1, maxDepth)
|
||||
)
|
||||
)
|
||||
} else if (type == RoomType.MESSAGING || type == null) {
|
||||
Timber.v("## SPACE_PEEK: room child $entry")
|
||||
spaceChildResults.add(
|
||||
SpaceChildPeekResult(
|
||||
childId, childPeek, entry.second?.default, entry.second?.order
|
||||
)
|
||||
)
|
||||
} else {
|
||||
// ignore for now?
|
||||
}
|
||||
|
||||
// let's check child info
|
||||
} catch (failure: Throwable) {
|
||||
// can this happen?
|
||||
Timber.e(failure, "## Failed to resolve space child")
|
||||
}
|
||||
}
|
||||
return spaceChildResults
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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 org.matrix.android.sdk.internal.session.space.peeking
|
||||
|
||||
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
|
||||
|
||||
data class SpacePeekSummary(
|
||||
val idOrAlias: String,
|
||||
val roomPeekResult: PeekResult.Success,
|
||||
val children: List<ISpaceChild>
|
||||
)
|
||||
|
||||
interface ISpaceChild {
|
||||
val id: String
|
||||
val roomPeekResult: PeekResult
|
||||
val default: Boolean?
|
||||
val order: String?
|
||||
}
|
||||
|
||||
data class SpaceChildPeekResult(
|
||||
override val id: String,
|
||||
override val roomPeekResult: PeekResult,
|
||||
override val default: Boolean? = null,
|
||||
override val order: String? = null
|
||||
) : ISpaceChild
|
||||
|
||||
data class SpaceSubChildPeekResult(
|
||||
override val id: String,
|
||||
override val roomPeekResult: PeekResult,
|
||||
override val default: Boolean?,
|
||||
override val order: String?,
|
||||
val children: List<ISpaceChild>
|
||||
) : ISpaceChild
|
||||
|
||||
sealed class SpacePeekResult {
|
||||
abstract class SpacePeekError : SpacePeekResult()
|
||||
data class FailedToResolve(val spaceId: String, val roomPeekResult: PeekResult) : SpacePeekError()
|
||||
data class NotSpaceType(val spaceId: String) : SpacePeekError()
|
||||
|
||||
data class Success(val summary: SpacePeekSummary): SpacePeekResult()
|
||||
}
|
|
@ -271,7 +271,8 @@
|
|||
<!-- </intent-filter>-->
|
||||
</activity>
|
||||
|
||||
<activity android:name=".features.devtools.RoomDevToolActivity" />
|
||||
<activity android:name=".features.devtools.RoomDevToolActivity"/>
|
||||
<activity android:name=".features.spaces.SpacePreviewActivity"/>
|
||||
<!-- Services -->
|
||||
|
||||
<service
|
||||
|
|
|
@ -118,6 +118,7 @@ import im.vector.app.features.settings.push.PushRulesFragment
|
|||
import im.vector.app.features.settings.threepids.ThreePidsSettingsFragment
|
||||
import im.vector.app.features.share.IncomingShareFragment
|
||||
import im.vector.app.features.signout.soft.SoftLogoutFragment
|
||||
import im.vector.app.features.spaces.preview.SpacePreviewFragment
|
||||
import im.vector.app.features.terms.ReviewTermsFragment
|
||||
import im.vector.app.features.usercode.ShowUserCodeFragment
|
||||
import im.vector.app.features.userdirectory.UserListFragment
|
||||
|
@ -630,4 +631,9 @@ interface FragmentModule {
|
|||
@IntoMap
|
||||
@FragmentKey(RoomDevToolSendFormFragment::class)
|
||||
fun bindRoomDevToolSendFormFragment(fragment: RoomDevToolSendFormFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(SpacePreviewFragment::class)
|
||||
fun bindSpacePreviewFragment(fragment: SpacePreviewFragment): Fragment
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel
|
|||
import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheetSharedActionViewModel
|
||||
import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilitySharedActionViewModel
|
||||
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleSharedActionViewModel
|
||||
import im.vector.app.features.spaces.SpacePreviewSharedActionViewModel
|
||||
import im.vector.app.features.userdirectory.UserListSharedActionViewModel
|
||||
|
||||
@Module
|
||||
|
@ -142,4 +143,9 @@ interface ViewModelModule {
|
|||
@IntoMap
|
||||
@ViewModelKey(DiscoverySharedViewModel::class)
|
||||
fun bindDiscoverySharedViewModel(viewModel: DiscoverySharedViewModel): ViewModel
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(SpacePreviewSharedActionViewModel::class)
|
||||
fun bindSpacePreviewSharedActionViewModel(viewModel: SpacePreviewSharedActionViewModel): ViewModel
|
||||
}
|
||||
|
|
|
@ -47,7 +47,8 @@ enum class Command(val command: String, val parameters: String, @StringRes val d
|
|||
DISCARD_SESSION("/discardsession", "", R.string.command_description_discard_session),
|
||||
CONFETTI("/confetti", "<message>", R.string.command_confetti),
|
||||
SNOW("/snow", "<message>", R.string.command_snow),
|
||||
CREATE_SPACE("/createspace", "<name> <invitee>*", R.string.command_description_create_space);
|
||||
CREATE_SPACE("/createspace", "<name> <invitee>*", R.string.command_description_create_space),
|
||||
ADD_TO_SPACE("/addToSpace", "spaceId", R.string.command_description_create_space);
|
||||
|
||||
val length
|
||||
get() = command.length + 1
|
||||
|
|
|
@ -296,7 +296,7 @@ object CommandParser {
|
|||
val message = textMessage.substring(Command.CONFETTI.command.length).trim()
|
||||
ParsedCommand.SendChatEffect(ChatEffect.CONFETTI, message)
|
||||
}
|
||||
Command.SNOW.command -> {
|
||||
Command.SNOW.command -> {
|
||||
val message = textMessage.substring(Command.SNOW.command.length).trim()
|
||||
ParsedCommand.SendChatEffect(ChatEffect.SNOW, message)
|
||||
}
|
||||
|
@ -312,6 +312,12 @@ object CommandParser {
|
|||
)
|
||||
}
|
||||
}
|
||||
Command.ADD_TO_SPACE.command -> {
|
||||
val rawCommand = textMessage.substring(Command.ADD_TO_SPACE.command.length).trim()
|
||||
ParsedCommand.AddToSpace(
|
||||
rawCommand
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
// Unknown command
|
||||
ParsedCommand.ErrorUnknownSlashCommand(slashCommand)
|
||||
|
|
|
@ -58,4 +58,5 @@ sealed class ParsedCommand {
|
|||
object DiscardSession : ParsedCommand()
|
||||
class SendChatEffect(val chatEffect: ChatEffect, val message: String) : ParsedCommand()
|
||||
class CreateSpace(val name: String, val invitees: List<String>) : ParsedCommand()
|
||||
class AddToSpace(val spaceId: String) : ParsedCommand()
|
||||
}
|
||||
|
|
|
@ -59,7 +59,8 @@ class SpaceListFragment @Inject constructor(
|
|||
views.groupListView.configureWith(spaceController)
|
||||
viewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is SpaceListViewEvents.OpenSpaceSummary -> sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup)
|
||||
is SpaceListViewEvents.OpenSpaceSummary -> sharedActionViewModel.post(HomeActivitySharedAction.OpenSpacePreview(it.id))
|
||||
is SpaceListViewEvents.OpenSpace -> sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup)
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,9 +18,13 @@
|
|||
package im.vector.app.features.grouplist
|
||||
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.ui.list.genericFooterItem
|
||||
import im.vector.app.core.ui.list.genericItemHeader
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.spaces.SpaceListViewState
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.space.SpaceSummary
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import javax.inject.Inject
|
||||
|
@ -50,24 +54,57 @@ class SpaceSummaryController @Inject constructor(
|
|||
if (summaries.isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
summaries.forEach { groupSummary ->
|
||||
val isSelected = groupSummary.spaceId == selected?.spaceId
|
||||
if (groupSummary.spaceId == ALL_COMMUNITIES_GROUP_ID) {
|
||||
homeSpaceSummaryItem {
|
||||
id(groupSummary.spaceId)
|
||||
selected(isSelected)
|
||||
listener { callback?.onSpaceSelected(groupSummary) }
|
||||
// show invites on top
|
||||
|
||||
summaries.filter { it.roomSummary.membership == Membership.INVITE }
|
||||
.let { invites ->
|
||||
if (invites.isNotEmpty()) {
|
||||
genericItemHeader {
|
||||
id("invites")
|
||||
text(stringProvider.getString(R.string.spaces_invited_header))
|
||||
}
|
||||
invites.forEach {
|
||||
spaceSummaryItem {
|
||||
avatarRenderer(avatarRenderer)
|
||||
id(it.spaceId)
|
||||
matrixItem(it.toMatrixItem())
|
||||
selected(false)
|
||||
listener { callback?.onSpaceSelected(it) }
|
||||
}
|
||||
}
|
||||
genericFooterItem {
|
||||
id("invite_space")
|
||||
text("")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
spaceSummaryItem {
|
||||
avatarRenderer(avatarRenderer)
|
||||
id(groupSummary.spaceId)
|
||||
matrixItem(groupSummary.toMatrixItem())
|
||||
selected(isSelected)
|
||||
listener { callback?.onSpaceSelected(groupSummary) }
|
||||
}
|
||||
}
|
||||
|
||||
genericItemHeader {
|
||||
id("spaces")
|
||||
text(stringProvider.getString(R.string.spaces_header))
|
||||
}
|
||||
|
||||
summaries
|
||||
.filter { it.roomSummary.membership == Membership.JOIN }
|
||||
.forEach { groupSummary ->
|
||||
|
||||
val isSelected = groupSummary.spaceId == selected?.spaceId
|
||||
if (groupSummary.spaceId == ALL_COMMUNITIES_GROUP_ID) {
|
||||
homeSpaceSummaryItem {
|
||||
id(groupSummary.spaceId)
|
||||
selected(isSelected)
|
||||
listener { callback?.onSpaceSelected(groupSummary) }
|
||||
}
|
||||
} else {
|
||||
spaceSummaryItem {
|
||||
avatarRenderer(avatarRenderer)
|
||||
id(groupSummary.spaceId)
|
||||
matrixItem(groupSummary.toMatrixItem())
|
||||
selected(isSelected)
|
||||
listener { callback?.onSpaceSelected(groupSummary) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
|
|
|
@ -54,6 +54,7 @@ import im.vector.app.features.popup.VerificationVectorAlert
|
|||
import im.vector.app.features.rageshake.VectorUncaughtExceptionHandler
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.app.features.settings.VectorSettingsActivity
|
||||
import im.vector.app.features.spaces.SpacePreviewActivity
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
|
||||
import im.vector.app.features.workers.signout.ServerBackupStatusViewState
|
||||
|
@ -141,6 +142,9 @@ class HomeActivity :
|
|||
views.drawerLayout.closeDrawer(GravityCompat.START)
|
||||
replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java, allowStateLoss = true)
|
||||
}
|
||||
is HomeActivitySharedAction.OpenSpacePreview -> {
|
||||
startActivity(SpacePreviewActivity.newIntent(this, sharedAction.spaceId))
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
.disposeOnDestroy()
|
||||
|
|
|
@ -25,4 +25,5 @@ sealed class HomeActivitySharedAction : VectorSharedAction {
|
|||
object OpenDrawer : HomeActivitySharedAction()
|
||||
object CloseDrawer : HomeActivitySharedAction()
|
||||
object OpenGroup : HomeActivitySharedAction()
|
||||
data class OpenSpacePreview(val spaceId: String) : HomeActivitySharedAction()
|
||||
}
|
||||
|
|
|
@ -839,6 +839,17 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
|
||||
popDraft()
|
||||
}
|
||||
is ParsedCommand.AddToSpace -> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
session.spaceService().getSpace(slashCommandResult.spaceId)?.addRoom(room.roomId)
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandResultError(failure))
|
||||
}
|
||||
}
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
|
||||
popDraft()
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
is SendMode.EDIT -> {
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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.app.features.spaces
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import com.airbnb.mvrx.MvRx
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.commitTransaction
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.app.databinding.ActivitySimpleBinding
|
||||
import im.vector.app.features.spaces.preview.SpacePreviewArgs
|
||||
import im.vector.app.features.spaces.preview.SpacePreviewFragment
|
||||
|
||||
class SpacePreviewActivity : VectorBaseActivity<ActivitySimpleBinding>() {
|
||||
|
||||
lateinit var sharedActionViewModel: SpacePreviewSharedActionViewModel
|
||||
|
||||
override fun getBinding(): ActivitySimpleBinding = ActivitySimpleBinding.inflate(layoutInflater)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
sharedActionViewModel = viewModelProvider.get(SpacePreviewSharedActionViewModel::class.java)
|
||||
sharedActionViewModel
|
||||
.observe()
|
||||
.subscribe { action ->
|
||||
when (action) {
|
||||
SpacePreviewSharedAction.DismissAction -> finish()
|
||||
SpacePreviewSharedAction.ShowModalLoading -> showWaitingView()
|
||||
SpacePreviewSharedAction.HideModalLoading -> hideWaitingView()
|
||||
is SpacePreviewSharedAction.ShowErrorMessage -> action.error?.let { showSnackbar(it) }
|
||||
}
|
||||
}.disposeOnDestroy()
|
||||
|
||||
if (isFirstCreation()) {
|
||||
val simpleName = SpacePreviewFragment::class.java.simpleName
|
||||
val args = intent?.getParcelableExtra<SpacePreviewArgs>(MvRx.KEY_ARG)
|
||||
if (supportFragmentManager.findFragmentByTag(simpleName) == null) {
|
||||
supportFragmentManager.commitTransaction {
|
||||
replace(R.id.simpleFragmentContainer,
|
||||
SpacePreviewFragment::class.java,
|
||||
Bundle().apply { this.putParcelable(MvRx.KEY_ARG, args) },
|
||||
simpleName
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newIntent(context: Context, spaceIdOrAlias: String): Intent {
|
||||
return Intent(context, SpacePreviewActivity::class.java).apply {
|
||||
putExtra(MvRx.KEY_ARG, SpacePreviewArgs(spaceIdOrAlias))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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.app.features.spaces
|
||||
|
||||
import im.vector.app.core.platform.VectorSharedAction
|
||||
import im.vector.app.core.platform.VectorSharedActionViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
sealed class SpacePreviewSharedAction : VectorSharedAction {
|
||||
object DismissAction : SpacePreviewSharedAction()
|
||||
object ShowModalLoading : SpacePreviewSharedAction()
|
||||
object HideModalLoading : SpacePreviewSharedAction()
|
||||
data class ShowErrorMessage(val error: String? = null) : SpacePreviewSharedAction()
|
||||
}
|
||||
|
||||
class SpacePreviewSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel<SpacePreviewSharedAction>()
|
|
@ -53,7 +53,8 @@ sealed class SpaceListAction : VectorViewModelAction {
|
|||
* Transient events for group list screen
|
||||
*/
|
||||
sealed class SpaceListViewEvents : VectorViewEvents {
|
||||
object OpenSpaceSummary : SpaceListViewEvents()
|
||||
object OpenSpace : SpaceListViewEvents()
|
||||
data class OpenSpaceSummary(val id: String) : SpaceListViewEvents()
|
||||
}
|
||||
|
||||
data class SpaceListViewState(
|
||||
|
@ -94,7 +95,7 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp
|
|||
// We only want to open group if the updated selectedGroup is a different one.
|
||||
if (currentGroupId != spaceSummary.spaceId) {
|
||||
currentGroupId = spaceSummary.spaceId
|
||||
_viewEvents.post(SpaceListViewEvents.OpenSpaceSummary)
|
||||
_viewEvents.post(SpaceListViewEvents.OpenSpace)
|
||||
}
|
||||
val optionGroup = Option.just(spaceSummary)
|
||||
selectedSpaceDataSource.post(optionGroup)
|
||||
|
@ -116,20 +117,27 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp
|
|||
// PRIVATE METHODS *****************************************************************************
|
||||
|
||||
private fun handleSelectSpace(action: SpaceListAction.SelectSpace) = withState { state ->
|
||||
if (state.selectedSpace?.spaceId != action.spaceSummary.spaceId) {
|
||||
// We take care of refreshing group data when selecting to be sure we get all the rooms and users
|
||||
// tryOrNull {
|
||||
// viewModelScope.launch {
|
||||
// session.getGroup(action.spaceSummary.groupId)?.fetchGroupData()
|
||||
|
||||
if (state.selectedSpace?.roomSummary?.membership == Membership.INVITE) {
|
||||
_viewEvents.post(SpaceListViewEvents.OpenSpaceSummary(state.selectedSpace.roomSummary.roomId))
|
||||
// viewModelScope.launch(Dispatchers.IO) {
|
||||
// tryOrNull { session.spaceService().peekSpace(action.spaceSummary.spaceId) }.let {
|
||||
// Timber.d("PEEK RESULT/ $it")
|
||||
// }
|
||||
// }
|
||||
setState { copy(selectedSpace = action.spaceSummary) }
|
||||
} else {
|
||||
if (state.selectedSpace?.spaceId != action.spaceSummary.spaceId) {
|
||||
// state.selectedSpace?.let {
|
||||
// selectedSpaceDataSource.post(Option.just(state.selectedSpace))
|
||||
// }
|
||||
setState { copy(selectedSpace = action.spaceSummary) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeGroupSummaries() {
|
||||
val roomSummaryQueryParams = roomSummaryQueryParams() {
|
||||
memberships = listOf(Membership.JOIN)
|
||||
memberships = listOf(Membership.JOIN, Membership.INVITE)
|
||||
displayName = QueryStringValue.IsNotEmpty
|
||||
excludeType = listOf(RoomType.MESSAGING, null)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.app.features.spaces.preview
|
||||
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.core.extensions.setTextOrHide
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_space_roomchild)
|
||||
abstract class RoomChildItem : VectorEpoxyModel<RoomChildItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute
|
||||
lateinit var roomId: String
|
||||
|
||||
@EpoxyAttribute
|
||||
lateinit var title: String
|
||||
|
||||
@EpoxyAttribute
|
||||
var topic: String? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
lateinit var memberCount: String
|
||||
|
||||
@EpoxyAttribute
|
||||
var avatarUrl: String? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
lateinit var avatarRenderer: AvatarRenderer
|
||||
|
||||
@EpoxyAttribute
|
||||
var depth: Int = 0
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
holder.roomNameText.text = title
|
||||
holder.roomTopicText.setTextOrHide(topic)
|
||||
holder.memberCountText.text = memberCount
|
||||
|
||||
avatarRenderer.render(
|
||||
MatrixItem.RoomItem(roomId, title, avatarUrl),
|
||||
holder.avatarImageView
|
||||
)
|
||||
holder.tabView.tabDepth = depth
|
||||
}
|
||||
|
||||
override fun unbind(holder: Holder) {
|
||||
avatarRenderer.clear(holder.avatarImageView)
|
||||
super.unbind(holder)
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val avatarImageView by bind<ImageView>(R.id.childRoomAvatar)
|
||||
val roomNameText by bind<TextView>(R.id.childRoomName)
|
||||
val roomTopicText by bind<TextView>(R.id.childRoomTopic)
|
||||
val memberCountText by bind<TextView>(R.id.spaceChildMemberCountText)
|
||||
val tabView by bind<SpaceTabView>(R.id.spaceChildTabView)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.app.features.spaces.preview
|
||||
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.ui.list.genericFooterItem
|
||||
import im.vector.app.core.ui.list.genericItemHeader
|
||||
import im.vector.app.core.utils.TextUtils
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
|
||||
import org.matrix.android.sdk.internal.session.space.peeking.ISpaceChild
|
||||
import org.matrix.android.sdk.internal.session.space.peeking.SpaceChildPeekResult
|
||||
import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult
|
||||
import org.matrix.android.sdk.internal.session.space.peeking.SpaceSubChildPeekResult
|
||||
import javax.inject.Inject
|
||||
|
||||
class SpacePreviewController @Inject constructor(
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val stringProvider: StringProvider
|
||||
) : TypedEpoxyController<SpacePreviewState>() {
|
||||
|
||||
interface InteractionListener
|
||||
|
||||
var interactionListener: InteractionListener? = null
|
||||
|
||||
override fun buildModels(data: SpacePreviewState?) {
|
||||
val result: SpacePeekResult = data?.peekResult?.invoke() ?: return
|
||||
|
||||
when (result) {
|
||||
is SpacePeekResult.SpacePeekError -> {
|
||||
genericFooterItem {
|
||||
id("failed")
|
||||
// TODO
|
||||
text("Failed to resolve")
|
||||
}
|
||||
}
|
||||
is SpacePeekResult.Success -> {
|
||||
// add summary info
|
||||
val memberCount = result.summary.roomPeekResult.numJoinedMembers ?: 0
|
||||
|
||||
spaceTopSummaryItem {
|
||||
id("info")
|
||||
formattedMemberCount(stringProvider.getQuantityString(R.plurals.room_title_members, memberCount, memberCount))
|
||||
topic(result.summary.roomPeekResult.topic ?: "")
|
||||
}
|
||||
|
||||
genericItemHeader {
|
||||
id("header_rooms")
|
||||
text(stringProvider.getString(R.string.rooms))
|
||||
}
|
||||
|
||||
buildChildren(result.summary.children, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildChildren(children: List<ISpaceChild>, depth: Int) {
|
||||
children.forEach { child ->
|
||||
when (child) {
|
||||
is SpaceSubChildPeekResult -> {
|
||||
when (val roomPeekResult = child.roomPeekResult) {
|
||||
is PeekResult.Success -> {
|
||||
subSpaceItem {
|
||||
id(roomPeekResult.roomId)
|
||||
roomId(roomPeekResult.roomId)
|
||||
title(roomPeekResult.name)
|
||||
depth(depth)
|
||||
avatarUrl(roomPeekResult.avatarUrl)
|
||||
avatarRenderer(avatarRenderer)
|
||||
}
|
||||
buildChildren(child.children, depth + 1)
|
||||
}
|
||||
else -> {
|
||||
// ?? TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
is SpaceChildPeekResult -> {
|
||||
// We have to check if the peek result was success
|
||||
when (val roomPeekResult = child.roomPeekResult) {
|
||||
is PeekResult.Success -> {
|
||||
roomChildItem {
|
||||
id(child.id)
|
||||
depth(depth)
|
||||
roomId(roomPeekResult.roomId)
|
||||
title(roomPeekResult.name ?: "")
|
||||
topic(roomPeekResult.topic ?: "")
|
||||
avatarUrl(roomPeekResult.avatarUrl)
|
||||
memberCount(TextUtils.formatCountToShortDecimal(roomPeekResult.numJoinedMembers ?: 0))
|
||||
avatarRenderer(avatarRenderer)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// What to do here?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.app.features.spaces.preview
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.jakewharton.rxbinding3.appcompat.navigationClicks
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.cleanup
|
||||
import im.vector.app.core.extensions.configureWith
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentSpacePreviewBinding
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.spaces.SpacePreviewSharedAction
|
||||
import im.vector.app.features.spaces.SpacePreviewSharedActionViewModel
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
@Parcelize
|
||||
data class SpacePreviewArgs(
|
||||
val idOrAlias: String
|
||||
) : Parcelable
|
||||
|
||||
class SpacePreviewFragment @Inject constructor(
|
||||
private val viewModelFactory: SpacePreviewViewModel.Factory,
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val epoxyController: SpacePreviewController
|
||||
) : VectorBaseFragment<FragmentSpacePreviewBinding>(), SpacePreviewViewModel.Factory {
|
||||
|
||||
private val viewModel by fragmentViewModel(SpacePreviewViewModel::class)
|
||||
lateinit var sharedActionViewModel: SpacePreviewSharedActionViewModel
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSpacePreviewBinding {
|
||||
return FragmentSpacePreviewBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
sharedActionViewModel = activityViewModelProvider.get(SpacePreviewSharedActionViewModel::class.java)
|
||||
}
|
||||
|
||||
override fun create(initialState: SpacePreviewState) = viewModelFactory.create(initialState)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewModel.observeViewEvents {
|
||||
handleViewEvents(it)
|
||||
}
|
||||
|
||||
views.roomPreviewNoPreviewToolbar.navigationClicks()
|
||||
.throttleFirst(300, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { sharedActionViewModel.post(SpacePreviewSharedAction.DismissAction) }
|
||||
.disposeOnDestroyView()
|
||||
|
||||
views.spacePreviewRecyclerView.configureWith(epoxyController)
|
||||
|
||||
views.spacePreviewAcceptInviteButton.debouncedClicks {
|
||||
viewModel.handle(SpacePreviewViewAction.AcceptInvite)
|
||||
}
|
||||
|
||||
views.spacePreviewDeclineInviteButton.debouncedClicks {
|
||||
viewModel.handle(SpacePreviewViewAction.DismissInvite)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
views.spacePreviewRecyclerView.cleanup()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(viewModel) {
|
||||
when (it.peekResult) {
|
||||
is Uninitialized,
|
||||
is Loading -> {
|
||||
views.spacePreviewPeekingProgress.isVisible = true
|
||||
views.spacePreviewButtonBar.isVisible = true
|
||||
views.spacePreviewAcceptInviteButton.isEnabled = false
|
||||
views.spacePreviewDeclineInviteButton.isEnabled = false
|
||||
}
|
||||
is Fail -> {
|
||||
views.spacePreviewPeekingProgress.isVisible = false
|
||||
views.spacePreviewButtonBar.isVisible = false
|
||||
}
|
||||
is Success -> {
|
||||
views.spacePreviewPeekingProgress.isVisible = false
|
||||
views.spacePreviewButtonBar.isVisible = true
|
||||
views.spacePreviewAcceptInviteButton.isEnabled = true
|
||||
views.spacePreviewDeclineInviteButton.isEnabled = true
|
||||
epoxyController.setData(it)
|
||||
}
|
||||
}
|
||||
updateToolbar(it)
|
||||
}
|
||||
|
||||
private fun handleViewEvents(viewEvents: SpacePreviewViewEvents) {
|
||||
when (viewEvents) {
|
||||
SpacePreviewViewEvents.Dismiss -> {
|
||||
}
|
||||
SpacePreviewViewEvents.StartJoining -> {
|
||||
sharedActionViewModel.post(SpacePreviewSharedAction.ShowModalLoading)
|
||||
}
|
||||
SpacePreviewViewEvents.JoinSuccess -> {
|
||||
sharedActionViewModel.post(SpacePreviewSharedAction.HideModalLoading)
|
||||
sharedActionViewModel.post(SpacePreviewSharedAction.DismissAction)
|
||||
}
|
||||
is SpacePreviewViewEvents.JoinFailure -> {
|
||||
sharedActionViewModel.post(SpacePreviewSharedAction.HideModalLoading)
|
||||
sharedActionViewModel.post(SpacePreviewSharedAction.ShowErrorMessage(viewEvents.message ?: getString(R.string.matrix_error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateToolbar(spacePreviewState: SpacePreviewState) {
|
||||
when (val preview = spacePreviewState.peekResult.invoke()) {
|
||||
is SpacePeekResult.Success -> {
|
||||
val roomPeekResult = preview.summary.roomPeekResult
|
||||
val mxItem = MatrixItem.RoomItem(roomPeekResult.roomId, roomPeekResult.name, roomPeekResult.avatarUrl)
|
||||
avatarRenderer.renderSpace(mxItem, views.spacePreviewToolbarAvatar)
|
||||
views.roomPreviewNoPreviewToolbarTitle.text = roomPeekResult.name
|
||||
}
|
||||
is SpacePeekResult.SpacePeekError,
|
||||
null -> {
|
||||
// what to do here?
|
||||
val mxItem = MatrixItem.RoomItem(spacePreviewState.idOrAlias, spacePreviewState.name, spacePreviewState.avatarUrl)
|
||||
avatarRenderer.renderSpace(mxItem, views.spacePreviewToolbarAvatar)
|
||||
views.roomPreviewNoPreviewToolbarTitle.text = spacePreviewState.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
viewModel.handle(SpacePreviewViewAction.ViewReady)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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.app.features.spaces.preview
|
||||
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult
|
||||
|
||||
data class SpacePreviewState(
|
||||
val idOrAlias: String,
|
||||
val name: String? = null,
|
||||
val avatarUrl: String? = null,
|
||||
val peekResult: Async<SpacePeekResult> = Uninitialized
|
||||
) : MvRxState {
|
||||
constructor(args: SpacePreviewArgs) : this(idOrAlias = args.idOrAlias)
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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.app.features.spaces.preview
|
||||
|
||||
import im.vector.app.core.platform.VectorViewModelAction
|
||||
|
||||
sealed class SpacePreviewViewAction : VectorViewModelAction {
|
||||
object ViewReady : SpacePreviewViewAction()
|
||||
object AcceptInvite : SpacePreviewViewAction()
|
||||
object DismissInvite : SpacePreviewViewAction()
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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.app.features.spaces.preview
|
||||
|
||||
import im.vector.app.core.platform.VectorViewEvents
|
||||
|
||||
sealed class SpacePreviewViewEvents : VectorViewEvents {
|
||||
object Dismiss: SpacePreviewViewEvents()
|
||||
object StartJoining: SpacePreviewViewEvents()
|
||||
object JoinSuccess: SpacePreviewViewEvents()
|
||||
data class JoinFailure(val message: String?): SpacePreviewViewEvents()
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.app.features.spaces.preview
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.airbnb.mvrx.ActivityViewModelContext
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
|
||||
import org.matrix.android.sdk.api.session.space.SpaceService
|
||||
import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult
|
||||
|
||||
class SpacePreviewViewModel @AssistedInject constructor(
|
||||
@Assisted private val initialState: SpacePreviewState,
|
||||
private val session: Session
|
||||
) : VectorViewModel<SpacePreviewState, SpacePreviewViewAction, SpacePreviewViewEvents>(initialState) {
|
||||
|
||||
private var initialized = false
|
||||
|
||||
init {
|
||||
// do we have some things in cache?
|
||||
session.getRoomSummary(initialState.idOrAlias)?.let {
|
||||
setState {
|
||||
copy(name = it.name, avatarUrl = it.avatarUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
fun create(initialState: SpacePreviewState): SpacePreviewViewModel
|
||||
}
|
||||
|
||||
companion object : MvRxViewModelFactory<SpacePreviewViewModel, SpacePreviewState> {
|
||||
override fun create(viewModelContext: ViewModelContext, state: SpacePreviewState): SpacePreviewViewModel? {
|
||||
val factory = when (viewModelContext) {
|
||||
is FragmentViewModelContext -> viewModelContext.fragment as? Factory
|
||||
is ActivityViewModelContext -> viewModelContext.activity as? Factory
|
||||
}
|
||||
return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
|
||||
}
|
||||
}
|
||||
|
||||
override fun handle(action: SpacePreviewViewAction) {
|
||||
when (action) {
|
||||
SpacePreviewViewAction.ViewReady -> handleReady()
|
||||
SpacePreviewViewAction.AcceptInvite -> handleAcceptInvite()
|
||||
SpacePreviewViewAction.DismissInvite -> handleDismissInvite()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDismissInvite() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
private fun handleAcceptInvite() = withState { state ->
|
||||
// Here we need to join the space himself as well as the default rooms in that space
|
||||
val spaceInfo = state.peekResult.invoke() as? SpacePeekResult.Success
|
||||
|
||||
// TODO if we have no summary, we cannot find auto join rooms...
|
||||
// So maybe we should trigger a retry on summary after the join?
|
||||
val spaceVia = (spaceInfo?.summary?.roomPeekResult as? PeekResult.Success)?.viaServers ?: emptyList()
|
||||
val autoJoinChildren = spaceInfo?.summary?.children
|
||||
?.filter { it.default == true }
|
||||
?.map {
|
||||
SpaceService.ChildAutoJoinInfo(
|
||||
it.id,
|
||||
// via servers
|
||||
(it.roomPeekResult as? PeekResult.Success)?.viaServers ?: emptyList()
|
||||
)
|
||||
} ?: emptyList()
|
||||
|
||||
// trigger modal loading
|
||||
_viewEvents.post(SpacePreviewViewEvents.StartJoining)
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val joinResult = session.spaceService().joinSpace(spaceInfo?.summary?.idOrAlias ?: initialState.idOrAlias, null, spaceVia, autoJoinChildren)
|
||||
when (joinResult) {
|
||||
SpaceService.JoinSpaceResult.Success,
|
||||
is SpaceService.JoinSpaceResult.PartialSuccess -> {
|
||||
// For now we don't handle partial success, it's just success
|
||||
_viewEvents.post(SpacePreviewViewEvents.JoinSuccess)
|
||||
}
|
||||
is SpaceService.JoinSpaceResult.Fail -> {
|
||||
_viewEvents.post(SpacePreviewViewEvents.JoinFailure(joinResult.error?.toString()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleReady() {
|
||||
if (!initialized) {
|
||||
initialized = true
|
||||
// peek for the room
|
||||
setState {
|
||||
copy(peekResult = Loading())
|
||||
}
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val result = session.spaceService().peekSpace(initialState.idOrAlias)
|
||||
setState {
|
||||
copy(peekResult = Success(result))
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
setState {
|
||||
copy(peekResult = Fail(failure))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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.app.features.spaces.preview
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.LinearLayout
|
||||
import im.vector.app.R
|
||||
|
||||
class SpaceTabView constructor(context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0)
|
||||
: LinearLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0) {}
|
||||
constructor(context: Context) : this(context, null, 0) {}
|
||||
|
||||
var tabDepth = 0
|
||||
set(value) {
|
||||
if (field != value) {
|
||||
field = value
|
||||
setUpView()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
setUpView()
|
||||
}
|
||||
|
||||
private fun setUpView() {
|
||||
// remove children
|
||||
removeAllViews()
|
||||
for (i in 0 until tabDepth) {
|
||||
inflate(context, R.layout.item_space_tab, this)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.app.features.spaces.preview
|
||||
|
||||
import android.widget.TextView
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.core.extensions.setTextOrHide
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_space_top_summary)
|
||||
abstract class SpaceTopSummaryItem : VectorEpoxyModel<SpaceTopSummaryItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute
|
||||
var topic: String? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
lateinit var formattedMemberCount: String
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
holder.spaceTopicText.setTextOrHide(topic)
|
||||
holder.memberCountText.text = formattedMemberCount
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val memberCountText by bind<TextView>(R.id.spaceSummaryMemberCountText)
|
||||
val spaceTopicText by bind<TextView>(R.id.spaceSummaryTopic)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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.app.features.spaces.preview
|
||||
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_space_subspace)
|
||||
abstract class SubSpaceItem : VectorEpoxyModel<SubSpaceItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute
|
||||
lateinit var roomId: String
|
||||
|
||||
@EpoxyAttribute
|
||||
lateinit var title: String
|
||||
|
||||
@EpoxyAttribute
|
||||
var avatarUrl: String? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
lateinit var avatarRenderer: AvatarRenderer
|
||||
|
||||
@EpoxyAttribute
|
||||
var depth: Int = 0
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
holder.nameText.text = title
|
||||
|
||||
avatarRenderer.renderSpace(
|
||||
MatrixItem.RoomItem(roomId, title, avatarUrl),
|
||||
holder.avatarImageView
|
||||
)
|
||||
holder.tabView.tabDepth = depth
|
||||
}
|
||||
|
||||
override fun unbind(holder: Holder) {
|
||||
avatarRenderer.clear(holder.avatarImageView)
|
||||
super.unbind(holder)
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val avatarImageView by bind<ImageView>(R.id.childSpaceAvatar)
|
||||
val nameText by bind<TextView>(R.id.childSpaceName)
|
||||
val tabView by bind<SpaceTabView>(R.id.childSpaceTab)
|
||||
}
|
||||
}
|
127
vector/src/main/res/layout/fragment_space_preview.xml
Normal file
127
vector/src/main/res/layout/fragment_space_preview.xml
Normal file
|
@ -0,0 +1,127 @@
|
|||
<?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:id="@+id/coordinatorLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/roomPreviewNoPreviewToolbar"
|
||||
style="@style/VectorToolbarStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?actionBarSize"
|
||||
app:navigationIcon="@drawable/ic_x_18dp"
|
||||
android:elevation="4dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<!-- <ImageView-->
|
||||
<!-- android:id="@+id/spacePreviewClose"-->
|
||||
<!-- android:layout_width="@dimen/layout_touch_size"-->
|
||||
<!-- android:layout_height="@dimen/layout_touch_size"-->
|
||||
<!-- android:clickable="true"-->
|
||||
<!-- android:focusable="true"-->
|
||||
<!-- android:foreground="?attr/selectableItemBackground"-->
|
||||
<!-- android:scaleType="center"-->
|
||||
<!-- android:src="@drawable/ic_x_18dp"-->
|
||||
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
|
||||
<!-- app:layout_constraintStart_toStartOf="parent"-->
|
||||
<!-- app:layout_constraintTop_toTopOf="parent" />-->
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/spacePreviewToolbarAvatar"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/roomPreviewNoPreviewToolbarTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?vctr_toolbar_primary_text_color"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toEndOf="@+id/spacePreviewToolbarAvatar"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@sample/matrix.json/data/displayName" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/spacePreviewPeekingProgress"
|
||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="14dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="?riotx_header_panel_background"
|
||||
android:indeterminate="true" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/spacePreviewRecyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:fastScrollEnabled="true"
|
||||
android:overScrollMode="always"
|
||||
android:scrollbars="vertical"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/spacePreviewPeekingProgress"
|
||||
tools:listitem="@layout/item_space_subspace" />
|
||||
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="?list_divider_color"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/spacePreviewButtonBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:elevation="2dp"
|
||||
android:orientation="horizontal"
|
||||
android:layout_margin="8dp">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/spacePreviewDeclineInviteButton"
|
||||
style="@style/VectorButtonStyleDestructive"
|
||||
android:layout_width="0dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/decline" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/spacePreviewAcceptInviteButton"
|
||||
style="@style/VectorButtonStylePositive"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/accept" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
115
vector/src/main/res/layout/item_space_roomchild.xml
Normal file
115
vector/src/main/res/layout/item_space_roomchild.xml
Normal file
|
@ -0,0 +1,115 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<im.vector.app.features.spaces.preview.SpaceTabView
|
||||
android:id="@+id/spaceChildTabView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:background="@drawable/space_home_background"
|
||||
android:clipToPadding="false"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/childRoomAvatar"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:scaleType="centerInside"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="MissingPrefix"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/childRoomName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toTopOf="@+id/childRoomTopic"
|
||||
app:layout_constraintEnd_toStartOf="@id/spaceChildMemberCountIcon"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/childRoomAvatar"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@tools:sample/full_names" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/childRoomTopic"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:textColor="?riotx_text_secondary"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/spaceChildBarrier"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="@+id/childRoomName"
|
||||
app:layout_constraintTop_toBottomOf="@+id/childRoomName"
|
||||
tools:text="@sample/matrix.json/data/roomTopic" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/spaceChildMemberCountIcon"
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:src="@drawable/ic_room_profile_member_list"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/spaceChildMemberCountText"
|
||||
app:layout_constraintEnd_toStartOf="@+id/spaceChildMemberCountText"
|
||||
app:layout_constraintTop_toTopOf="@+id/spaceChildMemberCountText"
|
||||
app:tint="?riotx_text_secondary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/spaceChildMemberCountText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:maxLength="5"
|
||||
android:textColor="?riotx_text_secondary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="123" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/spaceChildBarrier"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="childRoomAvatar,childRoomTopic"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="12dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/spaceChildBarrier" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
71
vector/src/main/res/layout/item_space_subspace.xml
Normal file
71
vector/src/main/res/layout/item_space_subspace.xml
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:background="?riotx_background"
|
||||
android:clipToPadding="false"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<im.vector.app.features.spaces.preview.SpaceTabView
|
||||
android:id="@+id/childSpaceTab"
|
||||
android:layout_width="3dp"
|
||||
android:layout_height="2dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/childSpaceAvatar"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/childSpaceAvatar"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:scaleType="centerInside"
|
||||
android:visibility="visible"
|
||||
android:layout_marginStart="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/childSpaceName"
|
||||
app:layout_constraintStart_toEndOf="@id/childSpaceTab"
|
||||
app:layout_constraintTop_toTopOf="@id/childSpaceName"
|
||||
tools:ignore="MissingPrefix"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<View
|
||||
android:layout_width="2dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:background="?riotx_list_bottom_sheet_divider_color"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:layout_marginStart="15dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/childSpaceAvatar">
|
||||
|
||||
</View>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/childSpaceName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/childSpaceAvatar"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@tools:sample/full_names" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
11
vector/src/main/res/layout/item_space_tab.xml
Normal file
11
vector/src/main/res/layout/item_space_tab.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<View
|
||||
android:layout_width="2dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_centerInParent="true"
|
||||
android:background="?riotx_list_bottom_sheet_divider_color" />
|
||||
</RelativeLayout>
|
46
vector/src/main/res/layout/item_space_top_summary.xml
Normal file
46
vector/src/main/res/layout/item_space_top_summary.xml
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/spaceSummaryMemberCountIcon"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:src="@drawable/ic_room_profile_member_list"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/spaceSummaryMemberCountText"
|
||||
app:layout_constraintStart_toEndOf="@+id/spaceSummaryMemberCountText"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="?riotx_text_secondary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/spaceSummaryMemberCountText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:textColor="?riotx_text_secondary"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/spaceSummaryMemberCountIcon"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/spaceSummaryMemberCountIcon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="123 members" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/spaceSummaryTopic"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:autoLink="all"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/spaceSummaryMemberCountIcon"
|
||||
tools:text="@sample/matrix.json/data/roomTopic" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -508,6 +508,9 @@
|
|||
<string name="groups_header">Communities</string>
|
||||
<string name="no_group_placeholder">No groups</string>
|
||||
|
||||
<string name="spaces_invited_header">Invites</string>
|
||||
<string name="spaces_header">Spaces</string>
|
||||
|
||||
<string name="send_bug_report_include_logs">Send logs</string>
|
||||
<string name="send_bug_report_include_crash_logs">Send crash logs</string>
|
||||
<string name="send_bug_report_include_key_share_history">Send key share requests history</string>
|
||||
|
|
Loading…
Reference in a new issue