Add Rooms to space

This commit is contained in:
Valere 2021-04-16 16:55:11 +02:00
parent 5a84456f1f
commit 8146d8ab1e
35 changed files with 1244 additions and 16 deletions

View file

@ -19,4 +19,5 @@ package org.matrix.android.sdk.api.query
sealed class ActiveSpaceFilter {
object None : ActiveSpaceFilter()
data class ActiveSpace(val currentSpaceId: String?) : ActiveSpaceFilter()
data class ExcludeSpace(val spaceId: String) : ActiveSpaceFilter()
}

View file

@ -178,13 +178,15 @@ interface RoomService {
* TODO Doc
*/
fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
pagedListConfig: PagedList.Config = defaultPagedListConfig): LiveData<PagedList<RoomSummary>>
pagedListConfig: PagedList.Config = defaultPagedListConfig,
sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): LiveData<PagedList<RoomSummary>>
/**
* TODO Doc
*/
fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
pagedListConfig: PagedList.Config = defaultPagedListConfig): UpdatableLivePageResult
pagedListConfig: PagedList.Config = defaultPagedListConfig,
sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): UpdatableLivePageResult
/**
* TODO Doc

View file

@ -0,0 +1,23 @@
/*
* Copyright 2021 The Matrix.org Foundation C.I.C.
*
* 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.api.session.room
enum class RoomSortOrder {
NAME,
ACTIVITY,
NONE
}

View file

@ -57,7 +57,8 @@ data class RoomSummary constructor(
val hasFailedSending: Boolean = false,
val roomType: String? = null,
val spaceParents: List<SpaceParentInfo>? = null,
val children: List<SpaceChildInfo>? = null
val children: List<SpaceChildInfo>? = null,
val flattenParentIds: List<String> = emptyList()
) {
val isVersioned: Boolean

View file

@ -89,7 +89,8 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
viaServers = it.viaServers.toList(),
parentRoomId = roomSummaryEntity.roomId
)
}
},
flattenParentIds = roomSummaryEntity.flattenParentIds?.split("|") ?: emptyList()
)
}
}

View file

@ -23,6 +23,7 @@ import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.api.session.room.RoomService
import org.matrix.android.sdk.api.session.room.RoomSortOrder
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
@ -91,14 +92,14 @@ internal class DefaultRoomService @Inject constructor(
return roomSummaryDataSource.getRoomSummariesLive(queryParams)
}
override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config)
override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config, sortOrder: RoomSortOrder)
: LiveData<PagedList<RoomSummary>> {
return roomSummaryDataSource.getSortedPagedRoomSummariesLive(queryParams, pagedListConfig)
return roomSummaryDataSource.getSortedPagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder)
}
override fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config)
override fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config, sortOrder: RoomSortOrder)
: UpdatableLivePageResult {
return roomSummaryDataSource.getUpdatablePagedRoomSummariesLive(queryParams, pagedListConfig)
return roomSummaryDataSource.getUpdatablePagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder)
}
override fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount {

View file

@ -28,6 +28,7 @@ import io.realm.Sort
import io.realm.kotlin.where
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
import org.matrix.android.sdk.api.query.RoomCategoryFilter
import org.matrix.android.sdk.api.session.room.RoomSortOrder
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
import org.matrix.android.sdk.api.session.room.model.Membership
@ -162,10 +163,22 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
}
fun getSortedPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
pagedListConfig: PagedList.Config): LiveData<PagedList<RoomSummary>> {
pagedListConfig: PagedList.Config,
sortOrder: RoomSortOrder): LiveData<PagedList<RoomSummary>> {
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
roomSummariesQuery(realm, queryParams)
.sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
.apply {
when (sortOrder) {
RoomSortOrder.NAME -> {
sort(RoomSummaryEntityFields.DISPLAY_NAME, Sort.ASCENDING)
}
RoomSortOrder.ACTIVITY -> {
sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
}
RoomSortOrder.NONE -> {
}
}
}
}
val dataSourceFactory = realmDataSourceFactory.map {
roomSummaryMapper.map(it)
@ -177,10 +190,22 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
}
fun getUpdatablePagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
pagedListConfig: PagedList.Config): UpdatableLivePageResult {
pagedListConfig: PagedList.Config,
sortOrder: RoomSortOrder): UpdatableLivePageResult {
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
roomSummariesQuery(realm, queryParams)
.sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
.apply {
when (sortOrder) {
RoomSortOrder.NAME -> {
sort(RoomSummaryEntityFields.DISPLAY_NAME, Sort.ASCENDING)
}
RoomSortOrder.ACTIVITY -> {
sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
}
RoomSortOrder.NONE -> {
}
}
}
}
val dataSourceFactory = realmDataSourceFactory.map {
roomSummaryMapper.map(it)
@ -197,7 +222,18 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
override fun updateQuery(builder: (RoomSummaryQueryParams) -> RoomSummaryQueryParams) {
realmDataSourceFactory.updateQuery {
roomSummariesQuery(it, builder.invoke(queryParams))
.sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
.apply {
when (sortOrder) {
RoomSortOrder.NAME -> {
sort(RoomSummaryEntityFields.DISPLAY_NAME, Sort.ASCENDING)
}
RoomSortOrder.ACTIVITY -> {
sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
}
RoomSortOrder.NONE -> {
}
}
}
}
}
}
@ -272,6 +308,9 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
query.contains(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, queryParams.activeSpaceId.currentSpaceId)
}
}
is ActiveSpaceFilter.ExcludeSpace -> {
query.not().contains(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, queryParams.activeSpaceId.spaceId)
}
else -> {
// nop
}

View file

@ -120,7 +120,8 @@ internal class DefaultSpaceService @Inject constructor(
avatarUrl = spaceDesc?.avatarUrl ?: "",
encryptionEventTs = null,
typingUsers = emptyList(),
isEncrypted = false
isEncrypted = false,
flattenParentIds = emptyList()
),
second = response.rooms
?.filter { it.roomId != spaceId }

View file

@ -161,7 +161,7 @@ Formatter\.formatShortFileSize===1
# android\.text\.TextUtils
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
enum class===98
enum class===99
### Do not import temporary legacy classes
import org.matrix.android.sdk.internal.legacy.riot===3

View file

@ -6,6 +6,7 @@
"message": "William Shakespeare (bapt. 26 April 1564 23 April 1616) was an English poet, playwright and actor, widely regarded as the greatest writer in the English language and the world's greatest dramatist. He is often called England's national poet and the \"Bard of Avon\". His extant works, including collaborations, consist of approximately 39 plays, 154 sonnets, two long narrative poems, and a few other verses, some of uncertain authorship. His plays have been translated into every major living language and are performed more often than those of any other playwright.\n\nShakespeare was born and raised in Stratford-upon-Avon, Warwickshire. At the age of 18, he married Anne Hathaway, with whom he had three children: Susanna and twins Hamnet and Judith. Sometime between 1585 and 1592, he began a successful career in London as an actor, writer, and part-owner of a playing company called the Lord Chamberlain's Men, later known as the King's Men. At age 49 (around 1613), he appears to have retired to Stratford, where he died three years later. Few records of Shakespeare's private life survive; this has stimulated considerable speculation about such matters as his physical appearance, his sexuality, his religious beliefs, and whether the works attributed to him were written by others. Such theories are often criticised for failing to adequately note that few records survive of most commoners of the period.\n\nShakespeare produced most of his known works between 1589 and 1613. His early plays were primarily comedies and histories and are regarded as some of the best work produced in these genres. Until about 1608, he wrote mainly tragedies, among them Hamlet, Othello, King Lear, and Macbeth, all considered to be among the finest works in the English language. In the last phase of his life, he wrote tragicomedies (also known as romances) and collaborated with other playwrights.\n\nMany of Shakespeare's plays were published in editions of varying quality and accuracy in his lifetime. However, in 1623, two fellow actors and friends of Shakespeare's, John Heminges and Henry Condell, published a more definitive text known as the First Folio, a posthumous collected edition of Shakespeare's dramatic works that included all but two of his plays. The volume was prefaced with a poem by Ben Jonson, in which Jonson presciently hails Shakespeare in a now-famous quote as \"not of an age, but for all time\".\n\nThroughout the 20th and 21st centuries, Shakespeare's works have been continually adapted and rediscovered by new movements in scholarship and performance. His plays remain popular and are studied, performed, and reinterpreted through various cultural and political contexts around the world.",
"roomName": "Matrix HQ",
"roomAlias": "#matrix:matrix.org",
"spaceName": "Runner's world",
"roomTopic": "Welcome to Matrix HQ! Here is the rest of the room topic, with a https://www.example.org url and a phone number: 0102030405 which should not be clickable."
},
{
@ -14,6 +15,7 @@
"message": "Hello!",
"roomName": "Room name very loooooooong with some details",
"roomAlias": "#matrix:matrix.org",
"spaceName": "Matrix Org",
"roomTopic": "Room topic very loooooooong with some details"
},
{
@ -22,6 +24,7 @@
"message": "How are you?",
"roomName": "Room name very loooooooong with some details",
"roomAlias": "#matrix:matrix.org",
"spaceName": "Rennes",
"roomTopic": "Room topic very loooooooong with some details"
},
{
@ -30,6 +33,7 @@
"message": "Great weather today!",
"roomName": "Room name very loooooooong with some details",
"roomAlias": "#matrix:matrix.org",
"spaceName": "Est London",
"roomTopic": "Room topic very loooooooong with some details"
},
{
@ -38,6 +42,7 @@
"message": "Let's do a picnic",
"roomName": "Room name very loooooooong with some details",
"roomAlias": "#matrix:matrix.org",
"spaceName": "Element HQ",
"roomTopic": "Room topic very loooooooong with some details"
},
{
@ -46,6 +51,7 @@
"message": "Yes, great idea",
"roomName": "Room name very loooooooong with some details",
"roomAlias": "#matrix:matrix.org",
"spaceName": "My Company",
"roomTopic": "Room topic very loooooooong with some details"
}
]

View file

@ -275,6 +275,7 @@
<activity android:name=".features.spaces.SpacePreviewActivity" />
<activity android:name=".features.spaces.SpaceExploreActivity" />
<activity android:name=".features.spaces.SpaceCreationActivity" />
<activity android:name=".features.spaces.manage.SpaceManageActivity" />
<!-- Services -->
<service

View file

@ -125,6 +125,7 @@ import im.vector.app.features.spaces.create.ChooseSpaceTypeFragment
import im.vector.app.features.spaces.create.CreateSpaceDefaultRoomsFragment
import im.vector.app.features.spaces.create.CreateSpaceDetailsFragment
import im.vector.app.features.spaces.explore.SpaceDirectoryFragment
import im.vector.app.features.spaces.manage.SpaceAddRoomFragment
import im.vector.app.features.spaces.preview.SpacePreviewFragment
import im.vector.app.features.terms.ReviewTermsFragment
import im.vector.app.features.usercode.ShowUserCodeFragment
@ -678,4 +679,9 @@ interface FragmentModule {
@IntoMap
@FragmentKey(ChoosePrivateSpaceTypeFragment::class)
fun bindChoosePrivateSpaceTypeFragment(fragment: ChoosePrivateSpaceTypeFragment): Fragment
@Binds
@IntoMap
@FragmentKey(SpaceAddRoomFragment::class)
fun bindSpaceAddRoomFragment(fragment: SpaceAddRoomFragment): Fragment
}

View file

@ -82,6 +82,7 @@ import im.vector.app.features.spaces.ShareSpaceBottomSheet
import im.vector.app.features.spaces.SpaceCreationActivity
import im.vector.app.features.spaces.SpaceExploreActivity
import im.vector.app.features.spaces.SpaceSettingsMenuBottomSheet
import im.vector.app.features.spaces.manage.SpaceManageActivity
import im.vector.app.features.terms.ReviewTermsActivity
import im.vector.app.features.ui.UiStateRepository
import im.vector.app.features.usercode.UserCodeActivity
@ -158,6 +159,7 @@ interface ScreenComponent {
fun inject(activity: RoomDevToolActivity)
fun inject(activity: SpaceCreationActivity)
fun inject(activity: SpaceExploreActivity)
fun inject(activity: SpaceManageActivity)
/* ==========================================================================================
* BottomSheets

View file

@ -35,10 +35,12 @@ import im.vector.app.features.navigation.Navigator
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
import im.vector.app.features.roomprofile.RoomProfileActivity
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.spaces.manage.SpaceManageActivity
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
import org.matrix.android.sdk.api.util.toMatrixItem
import timber.log.Timber
@ -91,7 +93,9 @@ class SpaceSettingsMenuBottomSheet : VectorBaseBottomSheetDialogFragment<BottomS
.subscribe { powerLevelContent ->
val powerLevelsHelper = PowerLevelsHelper(powerLevelContent)
val canInvite = powerLevelsHelper.isUserAbleToInvite(session.myUserId)
val canAddChild = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_SPACE_CHILD)
views.invitePeople.isVisible = canInvite
views.addRooms.isVisible = canAddChild
}.disposeOnDestroyView()
views.invitePeople.views.bottomSheetActionClickableZone.debouncedClicks {
@ -112,6 +116,10 @@ class SpaceSettingsMenuBottomSheet : VectorBaseBottomSheetDialogFragment<BottomS
startActivity(SpaceExploreActivity.newIntent(requireContext(), spaceArgs.spaceId))
}
views.addRooms.views.bottomSheetActionClickableZone.debouncedClicks {
startActivity(SpaceManageActivity.newIntent(requireContext(), spaceArgs.spaceId))
}
views.leaveSpace.views.bottomSheetActionClickableZone.debouncedClicks {
AlertDialog.Builder(requireContext())
.setMessage(getString(R.string.space_leave_prompt_msg))

View file

@ -0,0 +1,89 @@
/*
* 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.manage
import androidx.recyclerview.widget.DiffUtil
import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.paging.PagedListEpoxyController
import im.vector.app.core.utils.DebouncedClickListener
import im.vector.app.core.utils.createUIHandler
import im.vector.app.features.home.AvatarRenderer
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.RoomType
import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject
class AddRoomListController @Inject constructor(
private val avatarRenderer: AvatarRenderer
) : PagedListEpoxyController<RoomSummary>(
// Important it must match the PageList builder notify Looper
modelBuildingHandler = createUIHandler(),
itemDiffCallback = object : DiffUtil.ItemCallback<RoomSummary>() {
override fun areItemsTheSame(oldItem: RoomSummary, newItem: RoomSummary): Boolean {
return oldItem.roomId == newItem.roomId
}
override fun areContentsTheSame(oldItem: RoomSummary, newItem: RoomSummary): Boolean {
// for this use case we can test less things
return oldItem.displayName == newItem.displayName
&& oldItem.avatarUrl == newItem.avatarUrl
}
}
) {
interface Listener {
fun onItemSelected(roomSummary: RoomSummary)
}
var listener: Listener? = null
var ignoreRooms: List<String>? = null
var selectedItems: Map<String, Boolean> = emptyMap()
set(value) {
field = value
// mmm annoying but can't just force for a given model
requestForcedModelBuild()
}
override fun addModels(models: List<EpoxyModel<*>>) {
if (ignoreRooms == null) {
super.addModels(models)
} else {
super.addModels(
models.filter {
it !is RoomSelectionItem || !ignoreRooms!!.contains(it.matrixItem.id)
}
)
}
}
override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> {
if (item == null) return RoomSelectionPlaceHolderItem_().apply { id(currentPosition) }
return RoomSelectionItem_().apply {
id(item.roomId)
matrixItem(item.toMatrixItem())
avatarRenderer(this@AddRoomListController.avatarRenderer)
space(item.roomType == RoomType.SPACE)
selected(selectedItems[item.roomId] ?: false)
itemClickListener(DebouncedClickListener({
listener?.onItemSelected(item)
}))
}
}
}

View file

@ -0,0 +1,67 @@
/*
* 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.manage
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
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_room_to_add_in_space)
abstract class RoomSelectionItem : VectorEpoxyModel<RoomSelectionItem.Holder>() {
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute lateinit var matrixItem: MatrixItem
@EpoxyAttribute var space: Boolean = false
@EpoxyAttribute var selected: Boolean = false
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: View.OnClickListener? = null
override fun bind(holder: Holder) {
super.bind(holder)
if (space) {
avatarRenderer.renderSpace(matrixItem, holder.avatarImageView)
} else {
avatarRenderer.render(matrixItem, holder.avatarImageView)
}
holder.titleText.text = matrixItem.getBestName()
if (selected) {
holder.checkboxImage.setImageDrawable(ContextCompat.getDrawable(holder.view.context, R.drawable.ic_checkbox_on))
holder.checkboxImage.contentDescription = holder.view.context.getString(R.string.a11y_checked)
} else {
holder.checkboxImage.setImageDrawable(ContextCompat.getDrawable(holder.view.context, R.drawable.ic_checkbox_off))
holder.checkboxImage.contentDescription = holder.view.context.getString(R.string.a11y_unchecked)
}
holder.view.setOnClickListener {
itemClickListener?.onClick(it)
}
}
class Holder : VectorEpoxyHolder() {
val avatarImageView by bind<ImageView>(R.id.itemAddRoomRoomAvatar)
val titleText by bind<TextView>(R.id.itemAddRoomRoomNameText)
val checkboxImage by bind<ImageView>(R.id.itemAddRoomRoomCheckBox)
}
}

View file

@ -0,0 +1,27 @@
/*
* 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.manage
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
@EpoxyModelClass(layout = R.layout.item_room_to_add_in_space_placeholder)
abstract class RoomSelectionPlaceHolderItem : VectorEpoxyModel<RoomSelectionPlaceHolderItem.Holder>() {
class Holder : VectorEpoxyHolder()
}

View file

@ -0,0 +1,27 @@
/*
* 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.manage
import im.vector.app.core.platform.VectorViewModelAction
import org.matrix.android.sdk.api.session.room.model.RoomSummary
sealed class SpaceAddRoomActions : VectorViewModelAction {
data class UpdateFilter(val filter: String) : SpaceAddRoomActions()
data class ToggleSelection(val roomSummary: RoomSummary) : SpaceAddRoomActions()
object Save : SpaceAddRoomActions()
// object HandleBack : SpaceAddRoomActions()
}

View file

@ -0,0 +1,171 @@
/*
* 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.manage
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import com.jakewharton.rxbinding3.appcompat.queryTextChanges
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.OnBackPressed
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentSpaceAddRoomsBinding
import io.reactivex.rxkotlin.subscribeBy
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import java.util.concurrent.TimeUnit
import javax.inject.Inject
class SpaceAddRoomFragment @Inject constructor(
private val epoxyController: AddRoomListController,
private val viewModelFactory: SpaceAddRoomsViewModel.Factory
) : VectorBaseFragment<FragmentSpaceAddRoomsBinding>(),
OnBackPressed, AddRoomListController.Listener, SpaceAddRoomsViewModel.Factory {
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) =
FragmentSpaceAddRoomsBinding.inflate(layoutInflater, container, false)
private val viewModel by fragmentViewModel(SpaceAddRoomsViewModel::class)
private val sharedViewModel: SpaceManageSharedViewModel by activityViewModel()
override fun create(initialState: SpaceAddRoomsState): SpaceAddRoomsViewModel =
viewModelFactory.create(initialState)
override fun getMenuRes(): Int = R.menu.menu_space_add_room
private var saveNeeded = false
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
vectorBaseActivity.setSupportActionBar(views.addRoomToSpaceToolbar)
vectorBaseActivity.supportActionBar?.let {
it.setDisplayShowHomeEnabled(true)
it.setDisplayHomeAsUpEnabled(true)
}
// sharedActionViewModel = activityViewModelProvider.get(RoomDirectorySharedActionViewModel::class.java)
setupRecyclerView()
views.publicRoomsFilter.queryTextChanges()
.debounce(100, TimeUnit.MILLISECONDS)
.subscribeBy {
viewModel.handle(SpaceAddRoomActions.UpdateFilter(it.toString()))
}
.disposeOnDestroyView()
viewModel.selectionListLiveData.observe(viewLifecycleOwner) {
epoxyController.selectedItems = it
saveNeeded = it.values.any { it }
invalidateOptionsMenu()
}
viewModel.selectSubscribe(this, SpaceAddRoomsState::spaceName) {
views.appBarSpaceInfo.text = it
}.disposeOnDestroyView()
viewModel.selectSubscribe(this, SpaceAddRoomsState::ignoreRooms) {
epoxyController.ignoreRooms = it
}.disposeOnDestroyView()
viewModel.selectSubscribe(this, SpaceAddRoomsState::isSaving) {
if (it is Loading) {
sharedViewModel.handle(SpaceManagedSharedAction.ShowLoading)
} else {
sharedViewModel.handle(SpaceManagedSharedAction.HideLoading)
}
}.disposeOnDestroyView()
// views.createNewRoom.debouncedClicks {
// sharedActionViewModel.post(RoomDirectorySharedAction.CreateRoom)
// }
viewModel.observeViewEvents {
when (it) {
SpaceAddRoomsViewEvents.WarnUnsavedChanged -> {
AlertDialog.Builder(requireContext())
.setTitle(R.string.dialog_title_warning)
.setMessage(R.string.warning_unsaved_change)
.setPositiveButton(R.string.warning_unsaved_change_discard) { _, _ ->
sharedViewModel.handle(SpaceManagedSharedAction.HandleBack)
}
.setNegativeButton(R.string.cancel, null)
.show()
}
SpaceAddRoomsViewEvents.SaveFailed -> {
invalidateOptionsMenu()
}
SpaceAddRoomsViewEvents.SavedDone -> {
sharedViewModel.handle(SpaceManagedSharedAction.HandleBack)
}
}
}
}
override fun invalidate() = withState(viewModel) {
super.invalidate()
}
override fun onPrepareOptionsMenu(menu: Menu) {
super.onPrepareOptionsMenu(menu)
menu.findItem(R.id.spaceAddRoomSaveItem).isVisible = saveNeeded
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.spaceAddRoomSaveItem) {
viewModel.handle(SpaceAddRoomActions.Save)
return true
}
return super.onOptionsItemSelected(item)
}
override fun onDestroyView() {
views.roomList.cleanup()
epoxyController.listener = null
super.onDestroyView()
}
private fun setupRecyclerView() {
views.roomList.configureWith(epoxyController, showDivider = true)
epoxyController.listener = this
viewModel.updatableLivePageResult.livePagedList.observe(viewLifecycleOwner) {
epoxyController.submitList(it)
}
}
override fun onBackPressed(toolbarButton: Boolean): Boolean {
if (viewModel.canGoBack()) {
sharedViewModel.handle(SpaceManagedSharedAction.HandleBack)
}
return true
}
override fun onItemSelected(roomSummary: RoomSummary) {
viewModel.handle(SpaceAddRoomActions.ToggleSelection(roomSummary))
}
}

View file

@ -0,0 +1,35 @@
/*
* 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.manage
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
data class SpaceAddRoomsState(
// The current filter
val spaceId: String = "",
val currentFilter: String = "",
val spaceName: String = "",
val ignoreRooms: List<String> = emptyList(),
val isSaving: Async<List<String>> = Uninitialized
// val selectionList: Map<String, Boolean> = emptyMap()
) : MvRxState {
constructor(args: SpaceManageArgs) : this(
spaceId = args.spaceId
)
}

View file

@ -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.manage
import im.vector.app.core.platform.VectorViewEvents
sealed class SpaceAddRoomsViewEvents : VectorViewEvents {
object WarnUnsavedChanged : SpaceAddRoomsViewEvents()
object SavedDone : SpaceAddRoomsViewEvents()
object SaveFailed : SpaceAddRoomsViewEvents()
}

View file

@ -0,0 +1,194 @@
/*
* 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.manage
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import androidx.paging.PagedList
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 dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.core.platform.VectorViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.query.RoomCategoryFilter
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.RoomSortOrder
import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
class AddRoomError(val errorList: Map<String, Throwable>) : Throwable()
class SpaceAddRoomsViewModel @AssistedInject constructor(
@Assisted val initialState: SpaceAddRoomsState,
private val session: Session
) : VectorViewModel<SpaceAddRoomsState, SpaceAddRoomActions, SpaceAddRoomsViewEvents>(initialState) {
val updatableLivePageResult: UpdatableLivePageResult by lazy {
session.getFilteredPagedRoomSummariesLive(
roomSummaryQueryParams {
this.memberships = listOf(Membership.JOIN)
this.excludeType = null
this.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
this.activeSpaceId = ActiveSpaceFilter.ExcludeSpace(initialState.spaceId)
this.displayName = QueryStringValue.Contains(initialState.currentFilter, QueryStringValue.Case.INSENSITIVE)
},
pagedListConfig = PagedList.Config.Builder()
.setPageSize(10)
.setInitialLoadSizeHint(20)
.setEnablePlaceholders(true)
.setPrefetchDistance(10)
.build(),
sortOrder = RoomSortOrder.NAME
)
}
private val selectionList = mutableMapOf<String, Boolean>()
val selectionListLiveData = MutableLiveData<Map<String, Boolean>>()
init {
val spaceSummary = session.getRoomSummary(initialState.spaceId)
setState {
copy(
spaceName = spaceSummary?.displayName ?: "",
ignoreRooms = (spaceSummary?.flattenParentIds ?: emptyList()) + listOf(initialState.spaceId)
)
}
}
@AssistedFactory
interface Factory {
fun create(initialState: SpaceAddRoomsState): SpaceAddRoomsViewModel
}
companion object : MvRxViewModelFactory<SpaceAddRoomsViewModel, SpaceAddRoomsState> {
override fun create(viewModelContext: ViewModelContext, state: SpaceAddRoomsState): SpaceAddRoomsViewModel? {
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")
}
}
fun canGoBack(): Boolean {
val needToSave = selectionList.values.any { it }
if (needToSave) {
_viewEvents.post(SpaceAddRoomsViewEvents.WarnUnsavedChanged)
}
return !needToSave
}
override fun handle(action: SpaceAddRoomActions) {
when (action) {
is SpaceAddRoomActions.UpdateFilter -> {
updatableLivePageResult.updateQuery {
it.copy(
displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE)
)
}
setState {
copy(
currentFilter = action.filter
)
}
}
is SpaceAddRoomActions.ToggleSelection -> {
selectionList[action.roomSummary.roomId] = (selectionList[action.roomSummary.roomId] ?: false).not()
selectionListLiveData.postValue(selectionList.toMap())
}
SpaceAddRoomActions.Save -> {
doAddSelectedRooms()
}
}
}
private fun doAddSelectedRooms() {
val childrenToAdd = selectionList.filter { it.value }.keys
if (childrenToAdd.isEmpty()) return // should not happen
setState {
copy(
isSaving = Loading()
)
}
viewModelScope.launch {
val errors = mutableMapOf<String, Throwable>()
val completed = mutableListOf<String>()
childrenToAdd.forEach { roomId ->
try {
session.spaceService().getSpace(initialState.spaceId)!!.addChildren(
roomId = roomId,
viaServers = listOf(session.sessionParams.homeServerHost ?: ""),
order = null
)
completed.add(roomId)
} catch (failure: Throwable) {
errors[roomId] = failure
}
}
if (errors.isEmpty()) {
// success
withContext(Dispatchers.Main) {
setState {
copy(
isSaving = Success(childrenToAdd.toList())
)
}
completed.forEach {
selectionList.remove(it)
}
_viewEvents.post(SpaceAddRoomsViewEvents.SavedDone)
}
} else if (errors.size < childrenToAdd.size) {
// partial success
withContext(Dispatchers.Main) {
setState {
copy(
isSaving = Success(completed)
)
}
completed.forEach {
selectionList.remove(it)
}
_viewEvents.post(SpaceAddRoomsViewEvents.SavedDone)
}
} else {
// error
withContext(Dispatchers.Main) {
setState {
copy(
isSaving = Fail(AddRoomError(errors))
)
}
_viewEvents.post(SpaceAddRoomsViewEvents.SaveFailed)
}
}
}
}
}

View file

@ -0,0 +1,94 @@
/*
* 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.manage
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import androidx.core.view.isVisible
import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.viewModel
import im.vector.app.R
import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.extensions.commitTransaction
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivitySimpleBinding
import kotlinx.parcelize.Parcelize
import javax.inject.Inject
@Parcelize
data class SpaceManageArgs(
val spaceId: String
) : Parcelable
class SpaceManageActivity : VectorBaseActivity<ActivitySimpleBinding>(), SpaceManageSharedViewModel.Factory {
@Inject lateinit var sharedViewModelFactory: SpaceManageSharedViewModel.Factory
override fun injectWith(injector: ScreenComponent) {
injector.inject(this)
}
override fun getBinding(): ActivitySimpleBinding = ActivitySimpleBinding.inflate(layoutInflater)
override fun getTitleRes(): Int = R.string.space_add_existing_rooms
val sharedViewModel: SpaceManageSharedViewModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (isFirstCreation()) {
val simpleName = SpaceAddRoomFragment::class.java.simpleName
val args = intent?.getParcelableExtra<SpaceManageArgs>(MvRx.KEY_ARG)
if (supportFragmentManager.findFragmentByTag(simpleName) == null) {
supportFragmentManager.commitTransaction {
replace(R.id.simpleFragmentContainer,
SpaceAddRoomFragment::class.java,
Bundle().apply { this.putParcelable(MvRx.KEY_ARG, args) },
simpleName
)
}
}
}
sharedViewModel.observeViewEvents {
when (it) {
SpaceManagedSharedViewEvents.Finish -> {
finish()
}
SpaceManagedSharedViewEvents.HideLoading -> {
views.simpleActivityWaitingView.isVisible = false
}
SpaceManagedSharedViewEvents.ShowLoading -> {
views.simpleActivityWaitingView.isVisible = true
}
}
}
}
companion object {
fun newIntent(context: Context, spaceId: String): Intent {
return Intent(context, SpaceManageActivity::class.java).apply {
putExtra(MvRx.KEY_ARG, SpaceManageArgs(spaceId))
}
}
}
override fun create(initialState: SpaceManageViewState) = sharedViewModelFactory.create(initialState)
}

View file

@ -0,0 +1,59 @@
/*
* 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.manage
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.core.platform.VectorViewModel
import org.matrix.android.sdk.api.session.Session
class SpaceManageSharedViewModel @AssistedInject constructor(
@Assisted initialState: SpaceManageViewState,
private val session: Session
) : VectorViewModel<SpaceManageViewState, SpaceManagedSharedAction, SpaceManagedSharedViewEvents>(initialState) {
@AssistedFactory
interface Factory {
fun create(initialState: SpaceManageViewState): SpaceManageSharedViewModel
}
companion object : MvRxViewModelFactory<SpaceManageSharedViewModel, SpaceManageViewState> {
override fun create(viewModelContext: ViewModelContext, state: SpaceManageViewState): SpaceManageSharedViewModel? {
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: SpaceManagedSharedAction) {
when (action) {
SpaceManagedSharedAction.HandleBack -> {
// for now finish
_viewEvents.post(SpaceManagedSharedViewEvents.Finish)
}
SpaceManagedSharedAction.HideLoading -> _viewEvents.post(SpaceManagedSharedViewEvents.HideLoading)
SpaceManagedSharedAction.ShowLoading -> _viewEvents.post(SpaceManagedSharedViewEvents.ShowLoading)
}
}
}

View file

@ -0,0 +1,27 @@
/*
* 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.manage
import com.airbnb.mvrx.MvRxState
data class SpaceManageViewState(
val spaceId: String = ""
) : MvRxState {
constructor(args: SpaceManageArgs) : this(
spaceId = args.spaceId
)
}

View file

@ -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.manage
import im.vector.app.core.platform.VectorViewModelAction
sealed class SpaceManagedSharedAction : VectorViewModelAction {
object HandleBack : SpaceManagedSharedAction()
object ShowLoading: SpaceManagedSharedAction()
object HideLoading: SpaceManagedSharedAction()
}

View file

@ -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.manage
import im.vector.app.core.platform.VectorViewEvents
sealed class SpaceManagedSharedViewEvents : VectorViewEvents {
object Finish : SpaceManagedSharedViewEvents()
object ShowLoading: SpaceManagedSharedViewEvents()
object HideLoading: SpaceManagedSharedViewEvents()
}

View file

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:pathData="M4,0.75L12,0.75A3.25,3.25 0,0 1,15.25 4L15.25,12A3.25,3.25 0,0 1,12 15.25L4,15.25A3.25,3.25 0,0 1,0.75 12L0.75,4A3.25,3.25 0,0 1,4 0.75z"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#8D99A5"/>
</vector>

View file

@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:pathData="M4,0.75L12,0.75A3.25,3.25 0,0 1,15.25 4L15.25,12A3.25,3.25 0,0 1,12 15.25L4,15.25A3.25,3.25 0,0 1,0.75 12L0.75,4A3.25,3.25 0,0 1,4 0.75z"
android:strokeWidth="1.5"
android:fillColor="#03B381"
android:strokeColor="#03B381"/>
<path
android:pathData="M12,5.5L6.5,11L4,8.5"
android:strokeLineJoin="round"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:strokeLineCap="round"/>
</vector>

View file

@ -111,6 +111,18 @@
app:titleTextColor="?attr/riotx_text_primary"
tools:actionDescription="" />
<im.vector.app.core.ui.views.BottomSheetActionButton
android:id="@+id/addRooms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:actionTitle="@string/space_add_child_title"
app:leftIcon="@drawable/ic_add_black"
app:tint="?attr/riotx_text_primary"
app:titleTextColor="?attr/riotx_text_primary"
tools:actionDescription="" />
<im.vector.app.core.ui.views.BottomSheetActionButton
android:id="@+id/leaveSpace"
android:layout_width="match_parent"

View file

@ -0,0 +1,103 @@
<?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">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/roomList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?riotx_header_panel_background"
android:overScrollMode="always"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:listitem="@layout/item_room_to_add_in_space" />
<com.google.android.material.appbar.AppBarLayout
style="@style/VectorAppBarLayoutStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="4dp">
<!-- minHeight="0dp" is important to collapse on scroll -->
<androidx.appcompat.widget.Toolbar
android:id="@+id/addRoomToSpaceToolbar"
style="@style/VectorToolbarStyle"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:elevation="4dp"
android:minHeight="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap|enterAlways">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
android:orientation="vertical">
<TextView
android:id="@+id/appBarTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:gravity="start|center"
android:maxLines="1"
android:text="@string/space_add_existing_rooms"
android:textColor="?riotx_text_primary"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/appBarSpaceInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:gravity="start|center"
android:maxLines="1"
android:textColor="?riotx_text_secondary"
android:textSize="16sp"
tools:text="@sample/matrix.json/data/spaceName" />
</LinearLayout>
</androidx.appcompat.widget.Toolbar>
<androidx.appcompat.widget.SearchView
android:id="@+id/publicRoomsFilter"
style="@style/VectorSearchView"
android:layout_width="match_parent"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/addRoomToSpaceToolbar"
app:queryHint="@string/search_hint_room_name" />
<!-- <com.google.android.material.button.MaterialButton-->
<!-- android:id="@+id/createNewRoom"-->
<!-- style="@style/VectorButtonStyleText"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_marginStart="16dp"-->
<!-- android:layout_marginTop="8dp"-->
<!-- android:layout_marginBottom="8dp"-->
<!-- android:minHeight="@dimen/layout_touch_size"-->
<!-- android:text="@string/create_new_room"-->
<!-- app:icon="@drawable/ic_plus_circle"-->
<!-- app:iconPadding="13dp"-->
<!-- app:iconTint="@color/riotx_accent"-->
<!-- app:layout_constraintStart_toStartOf="parent"-->
<!-- app:layout_constraintTop_toBottomOf="@+id/addRoomToSpaceToolbar" />-->
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -0,0 +1,56 @@
<?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="?riotx_background"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
android:minHeight="50dp">
<ImageView
android:id="@+id/itemAddRoomRoomAvatar"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="16dp"
android:contentDescription="@string/avatar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/itemAddRoomRoomNameText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="17dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:maxLines="2"
android:textColor="?riotx_text_primary"
android:textSize="15sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/itemAddRoomRoomCheckBox"
app:layout_constraintStart_toEndOf="@id/itemAddRoomRoomAvatar"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="@sample/matrix.json/data/roomName" />
<ImageView
android:id="@+id/itemAddRoomRoomCheckBox"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginEnd="16dp"
android:contentDescription="@string/a11y_checked"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/itemAddRoomRoomNameText"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_checkbox_on" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,56 @@
<?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="?riotx_background"
android:clickable="true"
android:focusable="true"
android:minHeight="50dp">
<ImageView
android:id="@+id/itemAddRoomRoomAvatar"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="16dp"
android:importantForAccessibility="no"
android:src="@drawable/header_panel_round_background"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/itemAddRoomRoomNameText"
android:layout_width="0dp"
android:layout_height="16sp"
android:layout_marginStart="17dp"
android:layout_marginEnd="40dp"
android:background="@drawable/rounded_rect_shape_8"
android:ellipsize="end"
android:maxLines="2"
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/itemAddRoomRoomAvatar"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="@sample/matrix.json/data/roomName" />
<!-- <ImageView-->
<!-- android:id="@+id/itemAddRoomRoomCheckBox"-->
<!-- android:layout_width="20dp"-->
<!-- android:layout_height="20dp"-->
<!-- android:layout_marginEnd="16dp"-->
<!-- android:importantForAccessibility="no"-->
<!-- android:src="@drawable/ic_checkbox_off"-->
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
<!-- app:layout_constraintEnd_toEndOf="parent"-->
<!-- app:layout_constraintStart_toEndOf="@id/itemAddRoomRoomNameText"-->
<!-- app:layout_constraintTop_toTopOf="parent" />-->
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/spaceAddRoomSaveItem"
android:title="@string/save"
app:iconTint="?attr/colorAccent"
app:showAsAction="always" />
</menu>

View file

@ -2193,6 +2193,8 @@
<string name="room_directory_search_hint">Name or ID (#example:matrix.org)</string>
<string name="user_directory_search_hint">Search by name or ID</string>
<string name="search_hint_room_name">Search Name</string>
<string name="labs_swipe_to_reply_in_timeline">Enable swipe to reply in timeline</string>
<string name="labs_show_unread_notifications_as_tab">Add a dedicated tab for unread notifications on main screen.</string>
@ -3329,6 +3331,10 @@
</plurals>
<string name="space_explore_activity_title">Explore rooms</string>
<string name="space_add_child_title">Add rooms</string>
<string name="leave_space">Leave Space</string>
<string name="space_leave_prompt_msg">Are you sure you want to leave the space?</string>
<string name="space_add_existing_rooms">Add existing rooms and space</string>
</resources>