Compare commits

...

61 Commits

Author SHA1 Message Date
NIkita Fedrunov df25dfc284 patch version is bumped before shipping apk to participants, just to separate dev and testing posthog events from ones generated by real usage 2022-05-25 11:58:01 +02:00
NIkita Fedrunov 2ec468ba27 fixed ViewRoom tracking for Home 2022-05-24 17:24:30 +02:00
NIkita Fedrunov 484afac10b view room excessive tracks fixed, added active space property to space switch event 2022-05-24 17:01:55 +02:00
NIkita Fedrunov d70d110454 Merge branch 'experiment/nfe/space-switching-modal-analytics' into experiment/eric/space-switching-modal 2022-05-24 12:00:16 +02:00
NIkita Fedrunov 7437b71b24 analytics 2022-05-24 11:59:07 +02:00
ericdecanini 6da990d4a5 Disables toolbar click 2022-05-23 12:18:08 +02:00
ericdecanini 2d32d0da54 Adds space settings 2022-05-21 09:55:18 +02:00
ericdecanini f54da187ca Fixes crash when going into spaces 2022-05-20 13:25:27 +02:00
ericdecanini a961ed7de7 Makes options menu disappear on modal showing 2022-05-20 12:23:32 +02:00
ericdecanini df0415748f Fixes drawer being accessible from swiping 2022-05-20 11:44:16 +02:00
ericdecanini 43d23e195f Updates version name 2022-05-19 11:31:54 +02:00
ericdecanini 626c6933a3 Improves back navigation 2022-05-17 17:00:21 +02:00
ericdecanini 60adecedd6 Merge branch 'feature/eric/improve-back-navigation' into experiment/eric/space-switching-modal
# Conflicts:
#	vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt
#	vector/src/main/res/layout/fragment_home_detail.xml
2022-05-17 15:48:01 +02:00
ericdecanini 7f41aa313e Changes rotation style for modal chevron 2022-05-17 15:44:29 +02:00
ericdecanini c7efb2cec9 Changes between add space and create space 2022-05-17 15:28:28 +02:00
ericdecanini 10ceb56321 Adds labels to bottom navigation 2022-05-17 15:17:56 +02:00
ericdecanini 3a156cf8c8 Fixes app looking weird in non-light themes 2022-05-11 15:06:22 +02:00
ericdecanini 9618c76dbd Resizes space title 2022-05-11 11:35:00 +02:00
ericdecanini 456b6881d7 Adds invite for subspaces 2022-05-11 11:24:44 +02:00
ericdecanini 94e6e1b0d8 Shows or hides shadows based on scroll state 2022-05-11 11:05:26 +02:00
ericdecanini a6564b6466 Adds click handling of invites 2022-05-11 09:45:11 +02:00
ericdecanini c72ba8d651 Adds Invites bottoms sheet 2022-05-11 09:28:18 +02:00
ericdecanini a158d9866d Adds invites text to modal 2022-05-11 07:15:28 +02:00
ericdecanini 89db0645a5 Fixes space avatar not rendering 2022-05-10 21:14:33 +02:00
ericdecanini f5851a0bf8 Fixes space name turning to all chats when switching bot nav 2022-05-10 19:23:08 +02:00
ericdecanini 39e892dc08 Adds tint to arrow 2022-05-10 15:45:38 +02:00
ericdecanini 0faeada1c6 Adds animation to chevron 2022-05-10 14:13:50 +02:00
ericdecanini f3720d2dce Adds empty space and subspace splashes 2022-05-10 13:49:14 +02:00
ericdecanini 1bca6f83ee Adds shadows and max list size to modal 2022-05-10 13:00:18 +02:00
ericdecanini 4fe455c47b Back button text changes based on space parent 2022-05-10 09:19:34 +02:00
ericdecanini 65d5473661 Implements add sub spaces 2022-05-10 00:17:44 +02:00
ericdecanini e533b9f33d Adds space avatar to toolbar 2022-05-09 23:51:09 +02:00
ericdecanini 947cd8fceb Title changes on going back to all chats 2022-05-09 19:17:56 +02:00
ericdecanini a0baf77438 Merge branch 'feature/eric/replace-search-room-subheader' into experiment/eric/space-switching-modal 2022-05-09 16:58:27 +02:00
ericdecanini fee96b45bc Adds back button xml ui 2022-05-09 16:58:15 +02:00
ericdecanini 20a72e640b Modal changes space children 2022-05-09 15:12:18 +02:00
ericdecanini 952e3102d8 Merge remote-tracking branch 'origin/develop' into experiment/eric/space-switching-modal
# Conflicts:
#	vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
2022-05-06 17:03:42 +02:00
ericdecanini 19fcb6faf1 Adds click listener to add space 2022-05-06 17:02:59 +02:00
ericdecanini 89f69a1062 Adds space switching functionality 2022-05-06 15:50:53 +02:00
ericdecanini 4ee5b90f82 Implements hide modal on click dim view 2022-05-05 18:52:20 +02:00
ericdecanini 051adad0ed Fixes night mode colors 2022-05-05 15:51:28 +02:00
ericdecanini 2d2f8f5479 Adds elevation to dimview and modal 2022-05-05 14:53:19 +02:00
ericdecanini 9fc189efce Completes modal layout 2022-05-05 12:37:36 +02:00
ericdecanini b6ab0bdc53 Implements new toolbar design 2022-05-05 11:49:55 +02:00
ericdecanini 15c89f918e Adds space list modal 2022-05-05 09:20:24 +02:00
ericdecanini b46794d4df Adds changelog file 2022-05-02 14:44:23 +02:00
ericdecanini c9b32fec44 Changes ordering of room subtitles used 2022-05-02 14:42:56 +02:00
ericdecanini 47493fcfa1 Replaces method for getting the space parents of rooms 2022-05-02 14:11:17 +02:00
ericdecanini f70a24d257 Changes IncomingShareController display mode to FILTERED 2022-04-29 13:18:46 +02:00
ericdecanini a355b625e9 Adds displayMode to RoomSummaryListController 2022-04-29 13:05:08 +02:00
ericdecanini 7cc79fef0f Refactors RoomSummaryItem 2022-04-29 12:37:19 +02:00
ericdecanini 7e415e82b0 Fixes lint error 2022-04-28 12:37:54 +02:00
ericdecanini 962e9abc6b Fixes lint error 2022-04-28 12:04:54 +02:00
ericdecanini 4784717b0c Fixes lint error 2022-04-28 12:02:04 +02:00
ericdecanini b280358077 Adds more named arguments to RoomSummaryUpdater 2022-04-28 11:55:44 +02:00
ericdecanini 33475602f8 Adds canonical named argument to RoomSummaryUpdater 2022-04-28 11:54:51 +02:00
ericdecanini 87ad35dca6 Disables typing indicator in filtered search 2022-04-28 11:46:02 +02:00
ericdecanini 70cded2733 Adds user id and canonical alias to search result subtitles 2022-04-28 11:12:47 +02:00
ericdecanini 9e53e6cc8f Adds space name to rooms in filtered search 2022-04-28 10:41:40 +02:00
ericdecanini a3367d4075 Replaces else cases in when branches to RoomListDisplayMode.FILTERED 2022-04-26 15:48:49 +02:00
ericdecanini 0250f61d10 Replaces izPublic with isPublic 2022-04-26 15:48:34 +02:00
55 changed files with 2475 additions and 1098 deletions

File diff suppressed because it is too large Load Diff

View File

@ -104,7 +104,7 @@ allprojects {
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
// Warnings are potential errors, so stop ignoring them
// You can override by passing `-PallWarningsAsErrors=false` in the command line
kotlinOptions.allWarningsAsErrors = project.getProperties().getOrDefault("allWarningsAsErrors", "true").toBoolean()
// kotlinOptions.allWarningsAsErrors = project.getProperties().getOrDefault("allWarningsAsErrors", "true").toBoolean()
}
// Fix "Java heap space" issue

1
changelog.d/5860.feature Normal file
View File

@ -0,0 +1 @@
Adds space or user id as a subtitle under rooms in search

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="isDarkMode">true</bool>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="modal_background_color">@color/element_system_dark</color>
</resources>

View File

@ -3,5 +3,6 @@
<!-- Created to detect what has to be implemented (especially in the settings) -->
<bool name="false_not_implemented">false</bool>
<bool name="isDarkMode">false</bool>
</resources>
</resources>

View File

@ -36,6 +36,8 @@
<color name="android_status_bar_background_dark">@color/element_background_dark</color>
<color name="android_navigation_bar_background_dark">@color/element_system_dark</color>
<color name="modal_background_color">#FFFFFF</color>
<!-- Used for toolbar background -->
<attr name="vctr_toolbar_background" format="color" />

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="SpaceListModalImageShapeOverlay">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">8dp</item>
</style>
</resources>

View File

@ -17,6 +17,10 @@
<item name="android:textAppearance">@style/TextAppearance.Vector.Title.Medium</item>
</style>
<style name="Widget.Vector.TextView.Title.Bold">
<item name="android:textAppearance">@style/TextAppearance.Vector.Title.Bold</item>
</style>
<style name="Widget.Vector.TextView.HeadlineMedium">
<item name="android:textAppearance">@style/TextAppearance.Vector.Headline.Medium</item>
</style>
@ -48,4 +52,4 @@
<item name="lineHeight">16sp</item>
</style>
</resources>
</resources>

View File

@ -24,6 +24,11 @@
<item name="android:fontFamily">sans-serif-medium</item>
</style>
<style name="TextAppearance.Vector.Title.Bold">
<item name="fontFamily">sans-serif-black</item>
<item name="android:fontFamily">sans-serif-black</item>
</style>
<style name="TextAppearance.Vector.Headline.Medium" parent="TextAppearance.MaterialComponents.Headline1">
<item name="fontFamily">sans-serif-medium</item>
<item name="android:fontFamily">sans-serif-medium</item>

View File

@ -43,7 +43,6 @@ internal class RoomChildRelationInfo(
data class SpaceChildInfo(
val roomId: String,
val order: String?,
// val autoJoin: Boolean,
val viaServers: List<String>
)
@ -60,18 +59,13 @@ internal class RoomChildRelationInfo(
fun getDirectChildrenDescriptions(): List<SpaceChildInfo> {
return CurrentStateEventEntity.whereType(realm, roomId, EventType.STATE_SPACE_CHILD)
.findAll()
// .also {
// Timber.v("## Space: Found ${it.count()} m.space.child state events for $roomId")
// }
.mapNotNull {
ContentMapper.map(it.root?.content).toModel<SpaceChildContent>()?.let { scc ->
// Timber.v("## Space child desc state event $scc")
// Children where via is not present are ignored.
scc.via?.let { via ->
SpaceChildInfo(
roomId = it.stateKey,
order = scc.validOrder(),
// autoJoin = scc.autoJoin ?: false,
viaServers = via
)
}
@ -83,17 +77,13 @@ internal class RoomChildRelationInfo(
fun getParentDescriptions(): List<SpaceParentInfo> {
return CurrentStateEventEntity.whereType(realm, roomId, EventType.STATE_SPACE_PARENT)
.findAll()
// .also {
// Timber.v("## Space: Found ${it.count()} m.space.parent state events for $roomId")
// }
.mapNotNull {
ContentMapper.map(it.root?.content).toModel<SpaceParentContent>()?.let { scc ->
// Timber.v("## Space parent desc state event $scc")
ContentMapper.map(it.root?.content).toModel<SpaceParentContent>()?.let { spaceParentContent ->
// Parent where via is not present are ignored.
scc.via?.let { via ->
spaceParentContent.via?.let { via ->
SpaceParentInfo(
roomId = it.stateKey,
canonical = scc.canonical ?: false,
canonical = spaceParentContent.canonical ?: false,
viaServers = via,
stateEventSender = it.root?.sender ?: ""
)

View File

@ -36,6 +36,7 @@ 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.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.RoomType
import org.matrix.android.sdk.api.session.room.model.SpaceParentInfo
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.spaceSummaryQueryParams
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
@ -193,6 +194,18 @@ internal class RoomSummaryDataSource @Inject constructor(
}
val dataSourceFactory = realmDataSourceFactory.map {
roomSummaryMapper.map(it)
}.map { roomSummary ->
val parents = roomSummary.flattenParentIds.mapNotNull { parentId ->
getRoomSummary(parentId)?.let { parentSummary ->
SpaceParentInfo(
parentId = parentSummary.flattenParentIds.firstOrNull(),
roomSummary = parentSummary,
canonical = true,
viaServers = emptyList()
)
}
}
roomSummary.copy(spaceParents = parents)
}
val boundaries = MutableLiveData(ResultBoundaries())

View File

@ -18,7 +18,7 @@ ext.versionMinor = 4
// Note: even values are reserved for regular release, odd values for hotfix release.
// When creating a hotfix, you should decrease the value, since the current value
// is the value for the next regular release.
ext.versionPatch = 16
ext.versionPatch = 17
static def getGitTimestamp() {
def cmd = 'git show -s --format=%ct'
@ -262,7 +262,7 @@ android {
dimension "store"
isDefault = true
versionName "${versionMajor}.${versionMinor}.${versionPatch}${getGplayVersionSuffix()}"
versionName "${versionMajor}.${versionMinor}.${versionPatch}-experiment-spaceSwitching"
resValue "bool", "isGplay", "true"
buildConfigField "boolean", "ALLOW_FCM_USE", "true"
@ -273,7 +273,7 @@ android {
fdroid {
dimension "store"
versionName "${versionMajor}.${versionMinor}.${versionPatch}${getFdroidVersionSuffix()}"
versionName "${versionMajor}.${versionMinor}.${versionPatch}-experiment-spaceSwitching"
resValue "bool", "isGplay", "false"
buildConfigField "boolean", "ALLOW_FCM_USE", "false"

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) 2022 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
class SessionVariables {
companion object {
var optionsMenuShouldShow = true
}
}

View File

@ -46,6 +46,7 @@ import androidx.viewbinding.ViewBinding
import com.airbnb.mvrx.MavericksView
import com.bumptech.glide.util.Util
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.EntryPointAccessors
@ -238,6 +239,14 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
initUiAndData()
try {
val toolbarBackground = MaterialColors.getColor(views.root, R.attr.vctr_toolbar_background)
window.statusBarColor = toolbarBackground
window.navigationBarColor = toolbarBackground
} catch (e: Exception) {
}
val titleRes = getTitleRes()
if (titleRes != -1) {
supportActionBar?.let {

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2022 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.analytics.experiment
import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
data class ExperimentInteraction(
/**
* The unique name of this element.
*/
val name: Name,
val extra: Map<String, Any> = emptyMap()
) : VectorAnalyticsEvent {
enum class Name {
SpaceSwitchHeader,
SpaceSwitchHeaderAdd,
SpaceSwitchHeaderCreate,
SpacePanelSwitchSpace
}
override fun getName() = "Interaction"
override fun getProperties(): Map<String, Any>? {
return mutableMapOf<String, Any>().apply {
put("name", name.name)
putAll(extra)
}.takeIf { it.isNotEmpty() }
}
}

View File

@ -16,6 +16,7 @@
package im.vector.app.features.home
import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.net.Uri
@ -73,6 +74,15 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
)
}
@UiThread
fun render(matrixItem: MatrixItem, context: Context, imageView: ImageView) {
render(
GlideApp.with(context),
matrixItem,
DrawableImageViewTarget(imageView)
)
}
// fun renderSpace(matrixItem: MatrixItem, imageView: ImageView) {
// renderSpace(
// matrixItem,

View File

@ -37,6 +37,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.AppStateHandler
import im.vector.app.R
import im.vector.app.SessionVariables
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.registerStartForActivityResult
@ -190,7 +191,7 @@ class HomeActivity :
views.drawerLayout.addDrawerListener(drawerListener)
if (isFirstCreation()) {
replaceFragment(views.homeDetailFragmentContainer, HomeDetailFragment::class.java)
replaceFragment(views.homeDrawerFragmentContainer, HomeDrawerFragment::class.java)
// replaceFragment(views.homeDrawerFragmentContainer, HomeDrawerFragment::class.java)
}
sharedActionViewModel
@ -243,7 +244,7 @@ class HomeActivity :
}
private fun openGroup(shouldClearFragment: Boolean) {
views.drawerLayout.closeDrawer(GravityCompat.START)
// views.drawerLayout.closeDrawer(GravityCompat.START)
// When switching from space to group or group to space, we need to reload the fragment
if (shouldClearFragment) {
@ -270,7 +271,7 @@ class HomeActivity :
}
private fun closeGroup() {
views.drawerLayout.openDrawer(GravityCompat.START)
// views.drawerLayout.openDrawer(GravityCompat.START)
}
private fun handleShowAnalyticsOptIn() {
@ -520,11 +521,15 @@ class HomeActivity :
override fun getMenuRes() = R.menu.home
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
menu.findItem(R.id.menu_home_init_sync_legacy).isVisible = vectorPreferences.developerMode()
menu.findItem(R.id.menu_home_init_sync_optimized).isVisible = vectorPreferences.developerMode()
menu.findItem(R.id.menu_home_init_sync_legacy)?.isVisible = vectorPreferences.developerMode()
menu.findItem(R.id.menu_home_init_sync_optimized)?.isVisible = vectorPreferences.developerMode()
return super.onPrepareOptionsMenu(menu)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
return if (SessionVariables.optionsMenuShouldShow) super.onCreateOptionsMenu(menu) else false
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_home_suggestion -> {

View File

@ -19,11 +19,14 @@ package im.vector.app.features.home
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.animation.DecelerateInterpolator
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
@ -31,6 +34,7 @@ import com.google.android.material.badge.BadgeDrawable
import im.vector.app.AppStateHandler
import im.vector.app.R
import im.vector.app.RoomGroupingMethod
import im.vector.app.SessionVariables
import im.vector.app.core.extensions.commitTransaction
import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.core.platform.OnBackPressed
@ -41,6 +45,7 @@ import im.vector.app.core.ui.views.CurrentCallsView
import im.vector.app.core.ui.views.CurrentCallsViewPresenter
import im.vector.app.core.ui.views.KeysBackupBanner
import im.vector.app.databinding.FragmentHomeDetailBinding
import im.vector.app.features.analytics.experiment.ExperimentInteraction
import im.vector.app.features.call.SharedKnownCallsViewModel
import im.vector.app.features.call.VectorCallActivity
import im.vector.app.features.call.dialpad.DialPadFragment
@ -53,12 +58,18 @@ import im.vector.app.features.popup.VerificationVectorAlert
import im.vector.app.features.settings.VectorLocale
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.VectorSettingsActivity.Companion.EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS
import im.vector.app.features.spaces.SpaceExploreActivity
import im.vector.app.features.spaces.manage.ManageType
import im.vector.app.features.spaces.manage.SpaceManageActivity
import im.vector.app.features.spaces.share.ShareSpaceBottomSheet
import im.vector.app.features.themes.ThemeUtils
import im.vector.app.features.workers.signout.BannerState
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import org.matrix.android.sdk.api.session.group.model.GroupSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject
class HomeDetailFragment @Inject constructor(
@ -81,6 +92,8 @@ class HomeDetailFragment @Inject constructor(
private lateinit var sharedActionViewModel: HomeSharedActionViewModel
private lateinit var sharedCallActionViewModel: SharedKnownCallsViewModel
private var isInSpace = false
private var hasUnreadRooms = false
set(value) {
if (value != field) {
@ -97,16 +110,39 @@ class HomeDetailFragment @Inject constructor(
viewModel.handle(HomeDetailAction.MarkAllRoomsRead)
return true
}
R.id.menu_explore_rooms -> {
sharedActionViewModel.space.value?.let { startActivity(SpaceExploreActivity.newIntent(requireContext(), it.roomId)) }
return true
}
R.id.menu_invite_people -> {
activity?.let { activity ->
sharedActionViewModel.space.value?.let { ShareSpaceBottomSheet.show(activity.supportFragmentManager, it.roomId) }
}
return true
}
R.id.menu_add_rooms -> {
sharedActionViewModel.space.value?.let { startActivity(SpaceManageActivity.newIntent(requireActivity(), it.roomId, ManageType.AddRooms)) }
return true
}
}
return super.onOptionsItemSelected(item)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
if (SessionVariables.optionsMenuShouldShow) super.onCreateOptionsMenu(menu, inflater) else Unit
}
override fun onPrepareOptionsMenu(menu: Menu) {
withState(viewModel) { state ->
val isRoomList = state.currentTab is HomeTab.RoomList
menu.findItem(R.id.menu_home_mark_all_as_read).isVisible = isRoomList && hasUnreadRooms
menu.findItem(R.id.menu_home_mark_all_as_read)?.isVisible = isRoomList && hasUnreadRooms
}
menu.findItem(R.id.menu_explore_rooms)?.isVisible = isInSpace
menu.findItem(R.id.menu_invite_people)?.isVisible = isInSpace
menu.findItem(R.id.menu_add_rooms)?.isVisible = isInSpace
super.onPrepareOptionsMenu(menu)
}
@ -132,8 +168,13 @@ class HomeDetailFragment @Inject constructor(
viewModel.onEach(HomeDetailViewState::roomGroupingMethod) { roomGroupingMethod ->
when (roomGroupingMethod) {
is RoomGroupingMethod.ByLegacyGroup -> onGroupChange(roomGroupingMethod.groupSummary)
is RoomGroupingMethod.BySpace -> onSpaceChange(roomGroupingMethod.spaceSummary)
is RoomGroupingMethod.ByLegacyGroup -> {
onGroupChange(roomGroupingMethod.groupSummary)
}
is RoomGroupingMethod.BySpace -> {
onSpaceChange(roomGroupingMethod.spaceSummary)
sharedActionViewModel.space.value = roomGroupingMethod.spaceSummary
}
}
}
@ -141,12 +182,40 @@ class HomeDetailFragment @Inject constructor(
updateUIForTab(currentTab)
}
viewModel.onEach(HomeDetailViewState::showDialPadTab) { showDialPadTab ->
updateTabVisibilitySafely(R.id.bottom_action_dial_pad, showDialPadTab)
views.allChatsLayout.setOnClickListener {
toggleModalVisibility()
}
views.groupToolbarNavigateUp.setOnClickListener {
views.backButtonLayout.setOnClickListener {
navigateUpOneSpace()
// val currentSpace = sharedActionViewModel.space.value
// val directParent = currentSpace?.spaceParents?.firstOrNull()
// viewModel.handleSelectSpace(directParent?.roomSummary)
// sharedActionViewModel.space.value = directParent?.roomSummary
// sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup(false))
// onSpaceChange(directParent?.roomSummary)
}
views.dimView.setOnClickListener {
hideModal()
views.toolbarChevron.animate()
.rotation(0F)
.setDuration(300)
.setInterpolator(DecelerateInterpolator())
.start()
}
views.dimViewBottom.setOnClickListener {
hideModal()
views.toolbarChevron.animate()
.rotation(0F)
.setDuration(300)
.setInterpolator(DecelerateInterpolator())
.start()
}
viewModel.onEach(HomeDetailViewState::showDialPadTab) { showDialPadTab ->
updateTabVisibilitySafely(R.id.bottom_action_dial_pad, showDialPadTab)
}
viewModel.observeViewEvents { viewEvent ->
@ -189,6 +258,57 @@ class HomeDetailFragment @Inject constructor(
currentCallsViewPresenter.updateCall(callManager.getCurrentCall(), callManager.getCalls())
invalidateOptionsMenu()
}
observeSharedActions()
}
private fun observeSharedActions() = lifecycleScope.launch {
sharedActionViewModel.stream().collect { action ->
when (action) {
is HomeActivitySharedAction.OpenGroup -> hideModal()
else -> Unit
}
}
}
private fun toggleModalVisibility() {
if (views.spaceModalFragment.isVisible) {
hideModal()
views.toolbarChevron.animate()
.rotation(0F)
.setDuration(300)
.setInterpolator(DecelerateInterpolator())
.start()
} else {
val extraProperties = if (getCurrentSpace() != null) {
mapOf("isSubspace" to true)
} else {
mapOf("isSubspace" to false)
}
analyticsTracker.capture(ExperimentInteraction(ExperimentInteraction.Name.SpaceSwitchHeader, extraProperties))
showModal()
}
}
private fun showModal() {
SessionVariables.optionsMenuShouldShow = false
invalidateOptionsMenu()
views.spaceModalFragment.isVisible = true
views.dimView.isVisible = true
views.dimViewBottom.isVisible = true
views.toolbarChevron.animate()
.rotation(90F)
.setDuration(300)
.setInterpolator(DecelerateInterpolator())
.start()
}
private fun hideModal() {
SessionVariables.optionsMenuShouldShow = true
invalidateOptionsMenu()
views.spaceModalFragment.isVisible = false
views.dimView.isVisible = false
views.dimViewBottom.isVisible = false
}
private fun navigateUpOneSpace() {
@ -283,24 +403,45 @@ class HomeDetailFragment @Inject constructor(
}
private fun onGroupChange(groupSummary: GroupSummary?) {
hideModal()
if (groupSummary == null) {
views.backButtonLayout.isVisible = false
views.groupToolbarSpaceTitleView.isVisible = false
views.groupToolbarSpaceTitleView.text = getString(R.string.all_chats)
views.groupToolbarTitleView.text = getString(R.string.all_chats)
} else {
views.backButtonLayout.isVisible = true
views.groupToolbarSpaceTitleView.isVisible = true
views.groupToolbarSpaceTitleView.text = groupSummary.displayName
views.groupToolbarTitleView.text = groupSummary.displayName
}
}
private fun onSpaceChange(spaceSummary: RoomSummary?) {
hideModal()
views.backButtonText.text = getString(R.string.all_chats)
views.toolbarChevron.rotation = 0F
if (spaceSummary == null) {
isInSpace = false
invalidateOptionsMenu()
views.backButtonLayout.isVisible = false
views.groupToolbarSpaceTitleView.isVisible = false
views.groupToolbarAvatarImageView.isVisible = true
views.groupToolbarNavigateUp.isVisible = false
views.groupToolbarSpaceTitleView.text = getString(R.string.all_chats)
views.groupToolbarTitleView.text = getString(R.string.all_chats)
views.spaceAvatar.isVisible = false
} else {
isInSpace = true
invalidateOptionsMenu()
views.backButtonLayout.isVisible = true
views.groupToolbarSpaceTitleView.isVisible = true
views.groupToolbarSpaceTitleView.text = spaceSummary.displayName
views.groupToolbarAvatarImageView.isVisible = false
views.groupToolbarNavigateUp.isVisible = true
views.groupToolbarTitleView.text = spaceSummary.displayName
views.spaceAvatar.isVisible = true
avatarRenderer.render(spaceSummary.toMatrixItem(), requireContext(), views.spaceAvatar)
spaceSummary.spaceParents?.firstOrNull()?.let { directParent ->
views.backButtonText.text = directParent.roomSummary?.name ?: getString(R.string.all_chats)
}
}
}
@ -329,20 +470,20 @@ class HomeDetailFragment @Inject constructor(
sharedActionViewModel.post(HomeActivitySharedAction.OpenDrawer)
}
views.homeToolbarContent.debouncedClicks {
withState(viewModel) {
when (it.roomGroupingMethod) {
is RoomGroupingMethod.ByLegacyGroup -> {
// do nothing
}
is RoomGroupingMethod.BySpace -> {
it.roomGroupingMethod.spaceSummary?.let { spaceSummary ->
sharedActionViewModel.post(HomeActivitySharedAction.ShowSpaceSettings(spaceSummary.roomId))
}
}
}
}
}
// views.homeToolbarContent.debouncedClicks {
// withState(viewModel) {
// when (it.roomGroupingMethod) {
// is RoomGroupingMethod.ByLegacyGroup -> {
// // do nothing
// }
// is RoomGroupingMethod.BySpace -> {
// it.roomGroupingMethod.spaceSummary?.let { spaceSummary ->
// sharedActionViewModel.post(HomeActivitySharedAction.ShowSpaceSettings(spaceSummary.roomId))
// }
// }
// }
// }
// }
}
private fun setupBottomNavigationView() {
@ -361,7 +502,6 @@ class HomeDetailFragment @Inject constructor(
private fun updateUIForTab(tab: HomeTab) {
views.bottomNavigationView.menu.findItem(tab.toMenuId()).isChecked = true
views.groupToolbarTitleView.setText(tab.titleRes)
updateSelectedFragment(tab)
invalidateOptionsMenu()
}
@ -375,7 +515,9 @@ class HomeDetailFragment @Inject constructor(
childFragmentManager.fragments
.filter { it != fragmentToShow }
.forEach {
detach(it)
if (it.id != R.id.space_modal_fragment) {
detach(it)
}
}
if (fragmentToShow == null) {
when (tab) {

View File

@ -29,6 +29,10 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.extensions.singletonEntryPoint
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.VectorOverrides
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.extensions.toAnalyticsViewRoom
import im.vector.app.features.analytics.plan.Interaction
import im.vector.app.features.analytics.plan.ViewRoom
import im.vector.app.features.call.dialpad.DialPadLookup
import im.vector.app.features.call.lookup.CallProtocolsChecker
import im.vector.app.features.call.webrtc.WebRtcCallManager
@ -36,7 +40,10 @@ import im.vector.app.features.createdirect.DirectRoomHelper
import im.vector.app.features.invite.AutoAcceptInvites
import im.vector.app.features.invite.showInvites
import im.vector.app.features.settings.VectorDataStore
import im.vector.app.features.spaces.SpaceListAction
import im.vector.app.features.spaces.SpaceListViewEvents
import im.vector.app.features.ui.UiStateRepository
import im.vector.app.space
import im.vector.lib.core.utils.flow.throttleFirst
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.distinctUntilChanged
@ -52,6 +59,7 @@ import org.matrix.android.sdk.api.session.crypto.NewSessionListener
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
import org.matrix.android.sdk.api.session.room.RoomSortOrder
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.flow.flow
@ -70,7 +78,8 @@ class HomeDetailViewModel @AssistedInject constructor(
private val directRoomHelper: DirectRoomHelper,
private val appStateHandler: AppStateHandler,
private val autoAcceptInvites: AutoAcceptInvites,
private val vectorOverrides: VectorOverrides
private val vectorOverrides: VectorOverrides,
private val analyticsTracker: AnalyticsTracker
) : VectorViewModel<HomeDetailViewState, HomeDetailAction, HomeDetailViewEvents>(initialState),
CallProtocolsChecker.Listener {
@ -215,8 +224,26 @@ class HomeDetailViewModel @AssistedInject constructor(
}
}
fun handleSelectSpace(space: RoomSummary?) {
appStateHandler.setCurrentSpace(space?.roomId)
}
private fun trackSpaceSwitch(){
when (val groupingMethod = appStateHandler.getCurrentRoomGroupingMethod()) {
is RoomGroupingMethod.ByLegacyGroup -> {
// TODO!!
}
is RoomGroupingMethod.BySpace -> {
groupingMethod.spaceSummary.toAnalyticsViewRoom(null, groupingMethod).let {
analyticsTracker.capture(it)
}
}
}
}
private fun observeRoomSummaries() {
appStateHandler.selectedRoomGroupingFlow.distinctUntilChanged().flatMapLatest {
trackSpaceSwitch()
// we use it as a trigger to all changes in room, but do not really load
// the actual models
session.roomService().getPagedRoomSummariesLive(

View File

@ -16,8 +16,13 @@
package im.vector.app.features.home
import androidx.lifecycle.MutableLiveData
import im.vector.app.core.platform.VectorSharedActionViewModel
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import javax.inject.Inject
class HomeSharedActionViewModel @Inject constructor(val session: Session) : VectorSharedActionViewModel<HomeActivitySharedAction>()
class HomeSharedActionViewModel @Inject constructor(val session: Session) : VectorSharedActionViewModel<HomeActivitySharedAction>() {
val space = MutableLiveData<RoomSummary?>()
}

View File

@ -0,0 +1,71 @@
/*
* Copyright (c) 2022 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.home
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import im.vector.app.R
import im.vector.app.databinding.ListItemInviteBinding
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.util.toMatrixItem
class InvitesAdapter(
private val avatarRenderer: AvatarRenderer,
private val inviteUserTask: ((String) -> String?)?,
private val onInviteClicked: ((RoomSummary) -> Unit)?,
) : RecyclerView.Adapter<InvitesAdapter.ViewHolder>() {
private val invites = mutableListOf<RoomSummary>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = ListItemInviteBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(invites[position])
}
override fun getItemCount() = invites.size
@SuppressLint("NotifyDataSetChanged")
fun updateList(invites: List<RoomSummary>) {
this.invites.clear()
this.invites.addAll(invites)
notifyDataSetChanged()
}
inner class ViewHolder(private val binding: ListItemInviteBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(invite: RoomSummary) {
avatarRenderer.render(invite.toMatrixItem(), binding.avatar)
binding.name.text = invite.name
binding.root.setOnClickListener { onInviteClicked?.invoke(invite) }
invite.inviterId?.let {
val inviterName = inviteUserTask?.invoke(it)
if (inviterName != null) {
binding.invitedBy.text = binding.root.context.getString(R.string.invited_by, inviterName)
} else {
binding.invitedBy.text = ""
}
}
}
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright (c) 2022 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.home
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import im.vector.app.databinding.BottomSheetInvitesBinding
import org.matrix.android.sdk.api.session.room.model.RoomSummary
class InvitesBottomSheet(
private val invites: List<RoomSummary>,
private val avatarRenderer: AvatarRenderer,
private val inviteUserTask: ((String) -> String?)?,
private val onInviteClicked: ((RoomSummary) -> Unit)?
) : BottomSheetDialogFragment() {
private lateinit var binding: BottomSheetInvitesBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = BottomSheetInvitesBinding.inflate(inflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupInvitesList()
}
private fun setupInvitesList() {
val adapter = InvitesAdapter(avatarRenderer, inviteUserTask) {
dismiss()
onInviteClicked?.invoke(it)
}
val layoutManager = LinearLayoutManager(context)
binding.invitesList.adapter = adapter
binding.invitesList.layoutManager = layoutManager
adapter.updateList(invites)
}
companion object {
const val TAG = "InvitesBottomSheet"
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright (c) 2022 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.home
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import im.vector.app.databinding.ItemModalSpaceBinding
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.util.toMatrixItem
class SpaceListAdapter(
private val spaces: MutableList<RoomSummary>,
private val avatarRenderer: AvatarRenderer,
) : RecyclerView.Adapter<SpaceListAdapter.ViewHolder>() {
private var onItemClickListener: ((RoomSummary) -> Unit)? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView = ItemModalSpaceBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(itemView)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(spaces[position])
}
override fun getItemCount() = spaces.size
fun replaceList(spaces: List<RoomSummary>) {
this.spaces.clear()
this.spaces.addAll(spaces)
notifyDataSetChanged()
}
fun setOnSpaceClickListener(onClick: (space: RoomSummary) -> Unit) {
this.onItemClickListener = onClick
}
inner class ViewHolder(private val binding: ItemModalSpaceBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(space: RoomSummary) {
avatarRenderer.render(space.toMatrixItem(), binding.avatar)
binding.name.text = space.name
binding.root.setOnClickListener { onItemClickListener?.invoke(space) }
}
}
}

View File

@ -0,0 +1,137 @@
/*
* Copyright (c) 2022 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.home
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.allViews
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentSpaceListModalBinding
import im.vector.app.features.analytics.experiment.ExperimentInteraction
import im.vector.app.features.analytics.plan.Interaction
import im.vector.app.features.home.room.list.UnreadCounterBadgeView
import im.vector.app.features.spaces.SpaceListAction
import im.vector.app.features.spaces.SpaceListViewModel
import im.vector.app.features.spaces.manage.ManageType
import im.vector.app.features.spaces.manage.SpaceManageActivity
import timber.log.Timber
import javax.inject.Inject
@AndroidEntryPoint
class SpaceListModalFragment : VectorBaseFragment<FragmentSpaceListModalBinding>() {
private lateinit var sharedActionViewModel: HomeSharedActionViewModel
@Inject
lateinit var avatarRenderer: AvatarRenderer
private lateinit var binding: FragmentSpaceListModalBinding
private val viewModel: SpaceListViewModel by fragmentViewModel()
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSpaceListModalBinding {
binding = FragmentSpaceListModalBinding.inflate(inflater)
return binding
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java)
setupRecyclerView()
setupTopBar()
setupAddSpace()
setupInvites()
observeSpaceChange()
}
private fun setupRecyclerView() {
binding.roomList.layoutManager = LinearLayoutManager(context)
binding.roomList.adapter = SpaceListAdapter(mutableListOf(), avatarRenderer).apply {
setOnSpaceClickListener { spaceSummary ->
viewModel.handle(SpaceListAction.SelectSpace(spaceSummary))
sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup(false))
}
}
}
private fun setupTopBar() {
if (!binding.roomList.canScrollVertically(-1)) {
binding.headerBottomShadow.isVisible = false
binding.addSpaceTopShadow.isVisible = false
binding.addSpaceTopDivider.isVisible = true
} else {
binding.headerBottomShadow.isVisible = true
binding.addSpaceTopShadow.isVisible = true
binding.addSpaceTopDivider.isVisible = false
}
}
private fun setupAddSpace() {
binding.addSpaceLayout.setOnClickListener {
val currentSpace = sharedActionViewModel.space.value
if (currentSpace != null) {
analyticsTracker.capture(ExperimentInteraction(ExperimentInteraction.Name.SpaceSwitchHeaderAdd))
startActivity(SpaceManageActivity.newIntent(requireActivity(), currentSpace.roomId, ManageType.AddRoomsOnlySpaces))
} else {
analyticsTracker.capture(ExperimentInteraction(ExperimentInteraction.Name.SpaceSwitchHeaderCreate))
sharedActionViewModel.post(HomeActivitySharedAction.AddSpace)
}
}
}
private fun setupInvites() {
binding.invitesGroup.referencedIds.map { binding.root.findViewById<View>(it) }.forEach {
it.setOnClickListener {
withState(viewModel) { state ->
val invitesBottomSheet = InvitesBottomSheet(state.inviteSpaces.orEmpty(), avatarRenderer, state.inviteUserTask) { invite ->
sharedActionViewModel.post(HomeActivitySharedAction.OpenSpaceInvite(invite.roomId))
}
invitesBottomSheet.show(requireActivity().supportFragmentManager, InvitesBottomSheet.TAG)
}
}
}
}
private fun observeSpaceChange() = sharedActionViewModel.space.observe(viewLifecycleOwner) {
viewModel.setSpace(it)
binding.headerText.isVisible = it == null
binding.headerTextLayout.isVisible = binding.headerText.isVisible || binding.invitesGroup.isVisible
binding.addSpaceText.setText(if (it == null) R.string.create_space else R.string.add_space)
}
override fun invalidate() {
withState(viewModel) { state ->
state.rootSpacesOrdered?.let {
(binding.roomList.adapter as SpaceListAdapter).replaceList(it)
binding.noSpacesYetGroup.isVisible = it.isEmpty()
}
binding.invitesGroup.isVisible = state.inviteCount > 0
binding.headerTextLayout.isVisible = binding.headerText.isVisible || binding.invitesGroup.isVisible
binding.counterBadge.render(UnreadCounterBadgeView.State(state.inviteCount, true))
setupTopBar()
}
}
}

View File

@ -68,7 +68,7 @@ abstract class CollapsableTypedEpoxyController<T> :
}
override fun buildModels() {
check(isBuildingModels()) {
check(isBuildingModels) {
("You cannot call `buildModels` directly. Call `setData` instead to trigger a model " +
"refresh with new data.")
}

View File

@ -211,7 +211,7 @@ class RoomListFragment @Inject constructor(
RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.isVisible = true
RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.isVisible = true
RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.isVisible = true
else -> Unit // No button in this mode
RoomListDisplayMode.FILTERED -> Unit // No button in this mode
}
views.createChatRoomButton.debouncedClicks {
@ -237,7 +237,7 @@ class RoomListFragment @Inject constructor(
RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.hide()
RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.hide()
RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.hide()
else -> Unit
RoomListDisplayMode.FILTERED -> Unit
}
}
}
@ -294,7 +294,7 @@ class RoomListFragment @Inject constructor(
val contentAdapter =
when {
section.livePages != null -> {
pagedControllerFactory.createRoomSummaryPagedController()
pagedControllerFactory.createRoomSummaryPagedController(roomListParams.displayMode)
.also { controller ->
section.livePages.observe(viewLifecycleOwner) { pl ->
controller.submitList(pl)
@ -316,7 +316,7 @@ class RoomListFragment @Inject constructor(
)
}
}
section.isExpanded.observe(viewLifecycleOwner) { _ ->
section.isExpanded.observe(viewLifecycleOwner) {
refreshCollapseStates()
}
controller.listener = this
@ -337,14 +337,14 @@ class RoomListFragment @Inject constructor(
checkEmptyState()
}
observeItemCount(section, sectionAdapter)
section.isExpanded.observe(viewLifecycleOwner) { _ ->
section.isExpanded.observe(viewLifecycleOwner) {
refreshCollapseStates()
}
controller.listener = this
}
}
else -> {
pagedControllerFactory.createRoomSummaryListController()
pagedControllerFactory.createRoomSummaryListController(roomListParams.displayMode)
.also { controller ->
section.liveList?.observe(viewLifecycleOwner) { list ->
controller.setData(list)
@ -366,7 +366,7 @@ class RoomListFragment @Inject constructor(
)
}
}
section.isExpanded.observe(viewLifecycleOwner) { _ ->
section.isExpanded.observe(viewLifecycleOwner) {
refreshCollapseStates()
}
controller.listener = this
@ -402,7 +402,7 @@ class RoomListFragment @Inject constructor(
RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.show()
RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.show()
RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.show()
else -> Unit
RoomListDisplayMode.FILTERED -> Unit
}
}
}
@ -498,7 +498,7 @@ class RoomListFragment @Inject constructor(
isBigImage = true,
message = getString(R.string.room_list_rooms_empty_body)
)
else ->
RoomListDisplayMode.FILTERED ->
// Always display the content in this mode, because if the footer
StateView.State.Content
}

View File

@ -36,6 +36,7 @@ import im.vector.app.core.ui.views.PresenceStateImageView
import im.vector.app.core.ui.views.ShieldImageView
import im.vector.app.features.displayname.getBestName
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.themes.ThemeUtils
import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
@ -45,48 +46,102 @@ import org.matrix.android.sdk.api.util.MatrixItem
@EpoxyModelClass(layout = R.layout.item_room)
abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
@EpoxyAttribute lateinit var typingMessage: String
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute lateinit var matrixItem: MatrixItem
@EpoxyAttribute
lateinit var typingMessage: String
@EpoxyAttribute lateinit var lastFormattedEvent: EpoxyCharSequence
@EpoxyAttribute lateinit var lastEventTime: String
@EpoxyAttribute var encryptionTrustLevel: RoomEncryptionTrustLevel? = null
@EpoxyAttribute var userPresence: UserPresence? = null
@EpoxyAttribute var showPresence: Boolean = false
@EpoxyAttribute var izPublic: Boolean = false
@EpoxyAttribute var unreadNotificationCount: Int = 0
@EpoxyAttribute var hasUnreadMessage: Boolean = false
@EpoxyAttribute var hasDraft: Boolean = false
@EpoxyAttribute var showHighlighted: Boolean = false
@EpoxyAttribute var hasFailedSending: Boolean = false
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemLongClickListener: View.OnLongClickListener? = null
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: ClickListener? = null
@EpoxyAttribute var showSelected: Boolean = false
@EpoxyAttribute
lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute
lateinit var matrixItem: MatrixItem
@EpoxyAttribute
var displayMode: RoomListDisplayMode = RoomListDisplayMode.PEOPLE
@EpoxyAttribute
lateinit var subtitle: String
@EpoxyAttribute
lateinit var lastFormattedEvent: EpoxyCharSequence
@EpoxyAttribute
lateinit var lastEventTime: String
@EpoxyAttribute
var encryptionTrustLevel: RoomEncryptionTrustLevel? = null
@EpoxyAttribute
var userPresence: UserPresence? = null
@EpoxyAttribute
var showPresence: Boolean = false
@EpoxyAttribute @JvmField
var isPublic: Boolean = false
@EpoxyAttribute
var unreadNotificationCount: Int = 0
@EpoxyAttribute
var hasUnreadMessage: Boolean = false
@EpoxyAttribute
var hasDraft: Boolean = false
@EpoxyAttribute
var showHighlighted: Boolean = false
@EpoxyAttribute
var hasFailedSending: Boolean = false
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var itemLongClickListener: View.OnLongClickListener? = null
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var itemClickListener: ClickListener? = null
@EpoxyAttribute
var showSelected: Boolean = false
override fun bind(holder: Holder) {
super.bind(holder)
renderDisplayMode(holder)
holder.rootView.onClick(itemClickListener)
holder.rootView.setOnLongClickListener {
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
itemLongClickListener?.onLongClick(it) ?: false
}
holder.titleView.text = matrixItem.getBestName()
holder.lastEventTimeView.text = lastEventTime
holder.lastEventView.text = lastFormattedEvent.charSequence
holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted))
holder.unreadIndentIndicator.isVisible = hasUnreadMessage
holder.draftView.isVisible = hasDraft
avatarRenderer.render(matrixItem, holder.avatarImageView)
holder.roomAvatarDecorationImageView.render(encryptionTrustLevel)
holder.roomAvatarPublicDecorationImageView.isVisible = izPublic
holder.roomAvatarPublicDecorationImageView.isVisible = isPublic
holder.roomAvatarFailSendingImageView.isVisible = hasFailedSending
renderSelection(holder, showSelected)
holder.typingView.setTextOrHide(typingMessage)
holder.lastEventView.isInvisible = holder.typingView.isVisible
holder.roomAvatarPresenceImageView.render(showPresence, userPresence)
}
private fun renderDisplayMode(holder: Holder) = when (displayMode) {
RoomListDisplayMode.ROOMS,
RoomListDisplayMode.PEOPLE,
RoomListDisplayMode.NOTIFICATIONS -> renderForDefaultDisplayMode(holder)
RoomListDisplayMode.FILTERED -> renderForFilteredDisplayMode(holder)
}
private fun renderForDefaultDisplayMode(holder: Holder) {
holder.subtitleView.text = lastFormattedEvent.charSequence
holder.lastEventTimeView.text = lastEventTime
}
private fun renderForFilteredDisplayMode(holder: Holder) {
holder.subtitleView.text = subtitle
holder.typingView.setTextOrHide(typingMessage)
holder.subtitleView.isInvisible = holder.typingView.isVisible
}
override fun unbind(holder: Holder) {
holder.rootView.setOnClickListener(null)
holder.rootView.setOnLongClickListener(null)
@ -110,7 +165,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
val titleView by bind<TextView>(R.id.roomNameView)
val unreadCounterBadgeView by bind<UnreadCounterBadgeView>(R.id.roomUnreadCounterBadgeView)
val unreadIndentIndicator by bind<View>(R.id.roomUnreadIndicator)
val lastEventView by bind<TextView>(R.id.roomLastEventView)
val subtitleView by bind<TextView>(R.id.subtitleView)
val typingView by bind<TextView>(R.id.roomTypingView)
val draftView by bind<ImageView>(R.id.roomDraftBadge)
val lastEventTimeView by bind<TextView>(R.id.roomLastEventTimeView)

View File

@ -26,6 +26,7 @@ import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter
import im.vector.app.features.home.room.typing.TypingHelper
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
@ -46,13 +47,16 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
fun create(roomSummary: RoomSummary,
roomChangeMembershipStates: Map<String, ChangeMembershipState>,
selectedRoomIds: Set<String>,
displayMode: RoomListDisplayMode,
listener: RoomListListener?): VectorEpoxyModel<*> {
return when (roomSummary.membership) {
Membership.INVITE -> {
val changeMembershipState = roomChangeMembershipStates[roomSummary.roomId] ?: ChangeMembershipState.Unknown
createInvitationItem(roomSummary, changeMembershipState, listener)
}
else -> createRoomItem(roomSummary, selectedRoomIds, listener?.let { it::onRoomClicked }, listener?.let { it::onRoomLongClicked })
else -> createRoomItem(
roomSummary, selectedRoomIds, displayMode, listener?.let { it::onRoomClicked }, listener?.let { it::onRoomLongClicked }
)
}
}
@ -105,9 +109,11 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
fun createRoomItem(
roomSummary: RoomSummary,
selectedRoomIds: Set<String>,
displayMode: RoomListDisplayMode,
onClick: ((RoomSummary) -> Unit)?,
onLongClick: ((RoomSummary) -> Boolean)?
): VectorEpoxyModel<*> {
val subtitle = getSearchResultSubtitle(roomSummary)
val unreadCount = roomSummary.notificationCount
val showHighlighted = roomSummary.highlightCount > 0
val showSelected = selectedRoomIds.contains(roomSummary.roomId)
@ -118,13 +124,16 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
latestFormattedEvent = displayableEventFormatter.format(latestEvent, roomSummary.isDirect, roomSummary.isDirect.not())
latestEventTime = dateFormatter.format(latestEvent.root.originServerTs, DateFormatKind.ROOM_LIST)
}
val typingMessage = typingHelper.getTypingMessage(roomSummary.typingUsers)
return RoomSummaryItem_()
.id(roomSummary.roomId)
.avatarRenderer(avatarRenderer)
// We do not display shield in the room list anymore
// .encryptionTrustLevel(roomSummary.roomEncryptionTrustLevel)
.izPublic(roomSummary.isPublic)
.displayMode(displayMode)
.subtitle(subtitle)
.isPublic(roomSummary.isPublic)
.showPresence(roomSummary.isDirect)
.userPresence(roomSummary.directUserPresence)
.matrixItem(roomSummary.toMatrixItem())
@ -142,4 +151,12 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
}
.itemClickListener { onClick?.invoke(roomSummary) }
}
private fun getSearchResultSubtitle(roomSummary: RoomSummary): String {
val userId = roomSummary.directUserId
val spaceName = roomSummary.spaceParents?.firstOrNull()?.roomSummary?.name
val canonicalAlias = roomSummary.canonicalAlias
return (userId ?: spaceName ?: canonicalAlias).orEmpty()
}
}

View File

@ -16,17 +16,19 @@
package im.vector.app.features.home.room.list
import im.vector.app.features.home.RoomListDisplayMode
import org.matrix.android.sdk.api.session.room.model.RoomSummary
class RoomSummaryListController(
private val roomSummaryItemFactory: RoomSummaryItemFactory
private val roomSummaryItemFactory: RoomSummaryItemFactory,
private val displayMode: RoomListDisplayMode
) : CollapsableTypedEpoxyController<List<RoomSummary>>() {
var listener: RoomListListener? = null
override fun buildModels(data: List<RoomSummary>?) {
data?.forEach {
add(roomSummaryItemFactory.create(it, emptyMap(), emptySet(), listener))
add(roomSummaryItemFactory.create(it, emptyMap(), emptySet(), displayMode, listener))
}
}
}

View File

@ -19,11 +19,13 @@ package im.vector.app.features.home.room.list
import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.paging.PagedListEpoxyController
import im.vector.app.core.utils.createUIHandler
import im.vector.app.features.home.RoomListDisplayMode
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.RoomSummary
class RoomSummaryPagedController(
private val roomSummaryItemFactory: RoomSummaryItemFactory
private val roomSummaryItemFactory: RoomSummaryItemFactory,
private val displayMode: RoomListDisplayMode
) : PagedListEpoxyController<RoomSummary>(
// Important it must match the PageList builder notify Looper
modelBuildingHandler = createUIHandler()
@ -57,6 +59,6 @@ class RoomSummaryPagedController(
override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> {
// for place holder if enabled
item ?: return RoomSummaryItemPlaceHolder_().apply { id(currentPosition) }
return roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), listener)
return roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), displayMode, listener)
}
}

View File

@ -16,18 +16,19 @@
package im.vector.app.features.home.room.list
import im.vector.app.features.home.RoomListDisplayMode
import javax.inject.Inject
class RoomSummaryPagedControllerFactory @Inject constructor(
private val roomSummaryItemFactory: RoomSummaryItemFactory
) {
fun createRoomSummaryPagedController(): RoomSummaryPagedController {
return RoomSummaryPagedController(roomSummaryItemFactory)
fun createRoomSummaryPagedController(displayMode: RoomListDisplayMode): RoomSummaryPagedController {
return RoomSummaryPagedController(roomSummaryItemFactory, displayMode)
}
fun createRoomSummaryListController(): RoomSummaryListController {
return RoomSummaryListController(roomSummaryItemFactory)
fun createRoomSummaryListController(displayMode: RoomListDisplayMode): RoomSummaryListController {
return RoomSummaryListController(roomSummaryItemFactory, displayMode)
}
fun createSuggestedRoomListController(): SuggestedRoomListController {

View File

@ -22,6 +22,7 @@ import im.vector.app.R
import im.vector.app.core.epoxy.loadingItem
import im.vector.app.core.epoxy.noResultItem
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.home.room.list.RoomSummaryItemFactory
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import javax.inject.Inject
@ -53,7 +54,13 @@ class IncomingShareController @Inject constructor(private val roomSummaryItemFac
} else {
roomSummaries.forEach { roomSummary ->
roomSummaryItemFactory
.createRoomItem(roomSummary, data.selectedRoomIds, callback?.let { it::onRoomClicked }, callback?.let { it::onRoomLongClicked })
.createRoomItem(
roomSummary,
data.selectedRoomIds,
RoomListDisplayMode.FILTERED,
callback?.let { it::onRoomClicked },
callback?.let { it::onRoomLongClicked }
)
.addTo(this)
}
}

View File

@ -76,19 +76,16 @@ class SpaceListFragment @Inject constructor(
}
override fun onDragReleased(model: SpaceSummaryItem?, itemView: View?) {
// Timber.v("VAL: onModelMoved from $fromPositionM to $toPositionM ${model?.matrixItem?.getBestName()}")
if (toPositionM == null || fromPositionM == null) return
val movingSpace = model?.matrixItem?.id ?: return
viewModel.handle(SpaceListAction.MoveSpace(movingSpace, toPositionM!! - fromPositionM!!))
}
override fun clearView(model: SpaceSummaryItem?, itemView: View?) {
// Timber.v("VAL: clearView ${model?.matrixItem?.getBestName()}")
itemView?.elevation = initialElevation ?: 0f
}
override fun onModelMoved(fromPosition: Int, toPosition: Int, modelBeingMoved: SpaceSummaryItem?, itemView: View?) {
// Timber.v("VAL: onModelMoved incremental from $fromPosition to $toPosition ${modelBeingMoved?.matrixItem?.getBestName()}")
if (fromPositionM == null) {
fromPositionM = fromPosition
}
@ -97,7 +94,6 @@ class SpaceListFragment @Inject constructor(
}
override fun isDragEnabledForModel(model: SpaceSummaryItem?): Boolean {
// Timber.v("VAL: isDragEnabledForModel ${model?.matrixItem?.getBestName()}")
return model?.canDrag == true
}
})

View File

@ -17,6 +17,7 @@
package im.vector.app.features.spaces
import androidx.lifecycle.asFlow
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.Success
@ -29,7 +30,7 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.plan.Interaction
import im.vector.app.features.analytics.experiment.ExperimentInteraction
import im.vector.app.features.invite.AutoAcceptInvites
import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorPreferences
@ -50,10 +51,12 @@ import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.getUser
import org.matrix.android.sdk.api.session.group.groupSummaryQueryParams
import org.matrix.android.sdk.api.session.room.RoomSortOrder
import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.spaceSummaryQueryParams
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
@ -71,6 +74,8 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa
private val analyticsTracker: AnalyticsTracker
) : VectorViewModel<SpaceListViewState, SpaceListAction, SpaceListViewEvents>(initialState) {
private var currentSpace: RoomSummary? = null
@AssistedFactory
interface Factory : MavericksAssistedViewModelFactory<SpaceListViewModel, SpaceListViewState> {
override fun create(initialState: SpaceListViewState): SpaceListViewModel
@ -138,7 +143,7 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa
)
setState {
copy(
homeAggregateCount = counts
homeAggregateCount = counts,
)
}
}
@ -229,14 +234,13 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa
private fun handleSelectSpace(action: SpaceListAction.SelectSpace) = withState { state ->
val groupingMethod = state.selectedGroupingMethod
val isAtSpace = groupingMethod.space() != null
if (groupingMethod is RoomGroupingMethod.ByLegacyGroup || groupingMethod.space()?.roomId != action.spaceSummary?.roomId) {
analyticsTracker.capture(Interaction(null, null, Interaction.Name.SpacePanelSwitchSpace))
setState { copy(selectedGroupingMethod = RoomGroupingMethod.BySpace(action.spaceSummary)) }
appStateHandler.setCurrentSpace(action.spaceSummary?.roomId)
_viewEvents.post(SpaceListViewEvents.OpenSpace(groupingMethod is RoomGroupingMethod.ByLegacyGroup))
} else {
analyticsTracker.capture(Interaction(null, null, Interaction.Name.SpacePanelSelectedSpace))
}
analyticsTracker.capture(ExperimentInteraction(ExperimentInteraction.Name.SpacePanelSwitchSpace, mapOf("isSubspace" to isAtSpace)))
}
private fun handleSelectGroup(action: SpaceListAction.SelectLegacyGroup) = withState { state ->
@ -273,36 +277,43 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa
_viewEvents.post(SpaceListViewEvents.AddSpace)
}
var asyncSpaceList: Async<List<RoomSummary>>? = null
private fun observeSpaceSummaries() {
val params = spaceSummaryQueryParams {
memberships = listOf(Membership.JOIN, Membership.INVITE)
displayName = QueryStringValue.IsNotEmpty
}
combine(
session.flow()
.liveSpaceSummaries(params),
combine(session.flow().liveSpaceSummaries(params),
session.accountDataService()
.getLiveRoomAccountDataEvents(setOf(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER))
.asFlow()
) { spaces, _ ->
spaces
) { spaces, _ -> spaces }.execute { async ->
asyncSpaceList = async
val currentSpaceChildren = currentSpace?.let { space -> async.invoke()?.filter { it.flattenParentIds.contains(space.roomId) } }
val rootSpaces = async.invoke().orEmpty().filter { it.flattenParentIds.isEmpty() }
val displaySpaces = (currentSpaceChildren ?: rootSpaces).filter { it.inviterId == null }
val inviteSpaces = (currentSpaceChildren ?: rootSpaces).filter { it.inviterId != null }
val inviteUserTask: (String) -> String? = {
session.getUser(it)?.displayName
}
val orders = displaySpaces.associate {
it.roomId to session.getRoom(it.roomId)
?.roomAccountDataService()
?.getAccountDataEvent(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER)
?.content.toModel<SpaceOrderContent>()
?.safeOrder()
}
copy(
asyncSpaces = async,
rootSpacesOrdered = displaySpaces.sortedWith(TopLevelSpaceComparator(orders)),
inviteSpaces = inviteSpaces.sortedWith(TopLevelSpaceComparator(orders)),
inviteCount = inviteSpaces.size,
inviteUserTask = inviteUserTask,
spaceOrderInfo = orders
)
}
.execute { async ->
val rootSpaces = async.invoke().orEmpty().filter { it.flattenParentIds.isEmpty() }
val orders = rootSpaces.associate {
it.roomId to session.getRoom(it.roomId)
?.roomAccountDataService()
?.getAccountDataEvent(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER)
?.content.toModel<SpaceOrderContent>()
?.safeOrder()
}
copy(
asyncSpaces = async,
rootSpacesOrdered = rootSpaces.sortedWith(TopLevelSpaceComparator(orders)),
spaceOrderInfo = orders
)
}
// clear local echos on update
session.accountDataService()
@ -314,4 +325,36 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa
)
}
}
private fun emitSpaceViewState() = asyncSpaceList?.let { async ->
val currentSpaceChildren = currentSpace?.let { space -> asyncSpaceList?.invoke()?.filter { it.flattenParentIds.contains(space.roomId) } }
val rootSpaces = asyncSpaceList?.invoke().orEmpty().filter { it.flattenParentIds.isEmpty() }
val displaySpaces = (currentSpaceChildren ?: rootSpaces).filter { it.inviterId == null }
val inviteSpaces = (currentSpaceChildren ?: rootSpaces).filter { it.inviterId != null }
val inviteUserTask: (String) -> String? = {
session.getUser(it)?.displayName
}
val orders = displaySpaces.associate {
it.roomId to session.getRoom(it.roomId)
?.roomAccountDataService()
?.getAccountDataEvent(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER)
?.content.toModel<SpaceOrderContent>()
?.safeOrder()
}
setState {
copy(
asyncSpaces = async,
rootSpacesOrdered = displaySpaces.sortedWith(TopLevelSpaceComparator(orders)),
inviteSpaces = inviteSpaces.sortedWith(TopLevelSpaceComparator(orders)),
inviteCount = inviteSpaces.size,
inviteUserTask = inviteUserTask,
spaceOrderInfo = orders
)
}
}
fun setSpace(space: RoomSummary?) {
this.currentSpace = space
emitSpaceViewState()
}
}

View File

@ -30,9 +30,12 @@ data class SpaceListViewState(
val asyncSpaces: Async<List<RoomSummary>> = Uninitialized,
val selectedGroupingMethod: RoomGroupingMethod = RoomGroupingMethod.BySpace(null),
val rootSpacesOrdered: List<RoomSummary>? = null,
val inviteSpaces: List<RoomSummary>? = null,
val spaceOrderInfo: Map<String, String?>? = null,
val spaceOrderLocalEchos: Map<String, String?>? = null,
val legacyGroups: List<GroupSummary>? = null,
val expandedStates: Map<String, Boolean> = emptyMap(),
val inviteCount: Int = 0,
val inviteUserTask: ((String) -> String?)? = null,
val homeAggregateCount: RoomAggregateNotificationCount = RoomAggregateNotificationCount(0, 0)
) : MavericksState

View File

@ -0,0 +1,92 @@
/*
* 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 com.airbnb.epoxy.EpoxyController
import im.vector.app.R
import im.vector.app.RoomGroupingMethod
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.list.genericFooterItem
import im.vector.app.core.ui.list.genericHeaderItem
import im.vector.app.features.grouplist.groupSummaryItem
import im.vector.app.features.grouplist.homeSpaceSummaryItem
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.list.UnreadCounterBadgeView
import im.vector.app.group
import im.vector.app.space
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.group.model.GroupSummary
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject
class SpaceModalController @Inject constructor(
private val avatarRenderer: AvatarRenderer,
// private val colorProvider: ColorProvider,
// private val stringProvider: StringProvider
) : EpoxyController() {
var callback: Callback? = null
private var viewState: SpaceListViewState? = null
fun update(viewState: SpaceListViewState) {
this.viewState = viewState
requestModelBuild()
}
override fun buildModels() {
viewState?.apply {
buildGroupModels(selectedGroupingMethod, rootSpacesOrdered, expandedStates)
}
}
private fun buildGroupModels(selected: RoomGroupingMethod,
rootSpaces: List<RoomSummary>?,
expandedStates: Map<String, Boolean>) {
val host = this
rootSpaces?.forEach { spaceSummary ->
val isSelected = selected is RoomGroupingMethod.BySpace && spaceSummary.roomId == selected.space()?.roomId
val expanded = expandedStates[spaceSummary.roomId] == true
spaceSummaryItem {
avatarRenderer(host.avatarRenderer)
id(spaceSummary.roomId)
expanded(expanded)
matrixItem(spaceSummary.toMatrixItem())
selected(isSelected)
canDrag(true)
listener { host.callback?.onSpaceSelected(spaceSummary) }
countState(
UnreadCounterBadgeView.State(
spaceSummary.notificationCount,
spaceSummary.highlightCount > 0
)
)
}
}
}
interface Callback {
fun onSpaceSelected(spaceSummary: RoomSummary?)
}
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?attr/vctr_content_secondary">
<item android:drawable="@color/modal_background_color"/>
<!--this creates the mask with the ripple effect-->
<item
android:id="@+id/mask"
android:drawable="?attr/vctr_toolbar_background" />
</ripple>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?attr/vctr_toolbar_background"/>
<corners
android:bottomLeftRadius="12dp"
android:bottomRightRadius="12dp" />
</shape>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12.7822,4.2963C12.7822,3.8641 12.4318,3.5137 11.9996,3.5137C11.5673,3.5137 11.217,3.8641 11.217,4.2963V11.2173L4.2963,11.2173C3.8641,11.2173 3.5137,11.5676 3.5137,11.9999C3.5137,12.4321 3.8641,12.7825 4.2963,12.7825H11.217V19.7038C11.217,20.136 11.5673,20.4864 11.9996,20.4864C12.4318,20.4864 12.7822,20.136 12.7822,19.7038V12.7825H19.7038C20.136,12.7825 20.4864,12.4321 20.4864,11.9999C20.4864,11.5676 20.136,11.2173 19.7038,11.2173L12.7822,11.2173V4.2963Z"
android:fillColor="#17191C"
android:fillType="evenOdd"/>
</vector>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?attr/vctr_content_secondary">
<!--this creates the mask with the ripple effect-->
<item
android:id="@+id/mask"
android:drawable="?attr/vctr_toolbar_background" />
</ripple>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="#10000000"
android:endColor="@android:color/transparent"
android:angle="90" />
</shape>

View File

@ -22,10 +22,4 @@
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/homeDrawerFragmentContainer"
android:layout_width="@dimen/navigation_drawer_max_width"
android:layout_height="match_parent"
android:layout_gravity="start" />
</androidx.drawerlayout.widget.DrawerLayout>
</androidx.drawerlayout.widget.DrawerLayout>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="360dp"
android:background="?attr/vctr_toolbar_background"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/invites_header"
style="@style/Widget.Vector.TextView.Title.Medium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="@string/space_invites"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/invites_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@id/invites_header"
app:layout_constraintBottom_toBottomOf="parent"
tools:listitem="@layout/list_item_invite"
tools:itemCount="3"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -9,6 +9,7 @@
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:elevation="0dp"
app:layout_constraintTop_toTopOf="parent">
<im.vector.app.core.ui.views.CurrentCallsView
@ -18,19 +19,64 @@
android:minHeight="48dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/homeKeysBackupBanner"
tools:visibility="visible" />
tools:visibility="gone" />
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/groupToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize">
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="false"
android:gravity="center_vertical"
android:orientation="horizontal">
android:orientation="horizontal"
android:visibility="gone">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:orientation="vertical"
android:paddingStart="8dp"
android:paddingEnd="8dp"
tools:ignore="UselessParent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="start"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.Vector.Widget.ActionBarTitle"
android:textColor="?vctr_content_primary"
android:textStyle="bold"
tools:text="@tools:sample/lorem/random" />
<TextView
android:id="@+id/groupToolbarSpaceTitleView"
style="@style/TextAppearance.Vector.Widget.ActionBarSubTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="start"
android:maxLines="1"
android:textColor="?vctr_content_primary"
android:visibility="gone"
tools:text="@tools:sample/lorem/random"
tools:visibility="visible" />
</LinearLayout>
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/homeToolbarContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="visible">
<RelativeLayout
android:id="@+id/groupToolbarAvatarImageView"
@ -39,6 +85,7 @@
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:contentDescription="@string/a11y_open_drawer"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
@ -74,58 +121,83 @@
</RelativeLayout>
<ImageView
android:id="@+id/groupToolbarNavigateUp"
android:layout_width="28dp"
android:layout_height="28dp"
android:src="@drawable/ic_arrow_back"
android:layout_marginEnd="8dp"
android:contentDescription="@string/a11y_navigate_up_space"
android:visibility="gone"
app:tint="?vctr_content_secondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="MissingPrefix" />
<LinearLayout
android:id="@+id/homeToolbarContent"
android:layout_width="0dp"
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/back_button_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:orientation="vertical"
android:paddingStart="8dp"
android:paddingEnd="8dp">
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/back_button_chevron"
android:layout_width="20dp"
android:layout_height="20dp"
android:importantForAccessibility="no"
android:src="@drawable/ic_back_24dp"
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="?attr/vctr_message_text_color" />
<TextView
android:id="@+id/back_button_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginTop="16dp"
android:text="@string/chats"
android:visibility="gone"
android:textColor="@color/palette_element_green"
android:textSize="17sp"
app:layout_constraintStart_toEndOf="@id/back_button_chevron"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/all_chats_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:paddingBottom="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/back_button_layout">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/space_avatar"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="4dp"
android:visibility="gone"
app:shapeAppearanceOverlay="@style/SpaceListModalImageShapeOverlay"
tools:background="#42A5F5"
tools:visibility="visible" />
<TextView
android:id="@+id/groupToolbarTitleView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="start"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.Vector.Widget.ActionBarTitle"
android:textColor="?vctr_content_primary"
android:textStyle="bold"
tools:text="@tools:sample/lorem/random" />
<TextView
android:id="@+id/groupToolbarSpaceTitleView"
style="@style/TextAppearance.Vector.Widget.ActionBarSubTitle"
style="@style/Widget.Vector.TextView.Title.Bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="start"
android:maxLines="1"
android:textColor="?vctr_content_primary"
android:visibility="gone"
tools:text="@tools:sample/lorem/random"
tools:visibility="visible" />
android:layout_gravity="center_vertical"
android:layout_marginStart="4dp"
android:text="@string/all_chats"
android:textSize="22sp" />
</LinearLayout>
<ImageView
android:id="@+id/toolbar_chevron"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
android:layout_marginTop="2dp"
android:importantForAccessibility="no"
android:src="@drawable/ic_arrow_right"
app:tint="@color/palette_element_green"/>
</LinearLayout>
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.MaterialToolbar>
@ -149,12 +221,14 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/syncStateView"
tools:visibility="visible" />
tools:visibility="gone" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/roomListContainer"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="0dp"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@id/bottomNavigationView"
app:layout_constraintTop_toBottomOf="@id/homeKeysBackupBanner" />
@ -165,6 +239,46 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="@menu/home_bottom_navigation" />
app:menu="@menu/home_bottom_navigation"
app:labelVisibilityMode="labeled" />
<View
android:id="@+id/dim_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:alpha="0.5"
android:background="#000"
android:elevation="0dp"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/bottomNavigationView"
app:layout_constraintTop_toBottomOf="@id/appBarLayout" />
<View
android:id="@+id/dim_view_bottom"
android:layout_width="match_parent"
android:layout_height="0dp"
android:alpha="0.5"
android:background="#000"
android:elevation="10dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/bottomNavigationView"
app:layout_constraintTop_toTopOf="@id/bottomNavigationView" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/space_modal_fragment"
android:name="im.vector.app.features.home.SpaceListModalFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="@id/bottom_guideline"
android:elevation="0dp"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/appBarLayout" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/bottom_guideline"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.75" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,188 @@
<?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="match_parent"
android:background="@drawable/bg_space_modal">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/header_text_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
android:background="?attr/vctr_toolbar_background"
android:paddingBottom="8dp">
<TextView
android:id="@+id/header_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:textSize="13sp"
android:textColor="?attr/vctr_content_tertiary"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp"
android:text="@string/choose_a_space"
android:textAllCaps="true" />
<androidx.constraintlayout.widget.Group
android:id="@+id/invites_group"
android:layout_width="0dp"
android:layout_height="0dp"
app:constraint_referenced_ids="invites_text, counter_badge"
android:visibility="gone"
tools:visibility="visible" />
<TextView
android:id="@+id/invites_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textSize="13sp"
android:textColor="?attr/vctr_content_tertiary"
android:layout_marginEnd="8dp"
android:text="@string/invites"
android:textAllCaps="true"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@id/counter_badge" />
<im.vector.app.features.home.room.list.UnreadCounterBadgeView
android:id="@+id/counter_badge"
style="@style/Widget.Vector.TextView.Micro"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:minWidth="16dp"
android:minHeight="16dp"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:textColor="?colorOnError"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/invites_text"
app:layout_constraintBottom_toBottomOf="@id/invites_text"
tools:background="@drawable/bg_unread_highlight"
tools:text="147" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/room_list"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/header_text_layout"
app:layout_constraintBottom_toTopOf="@id/add_space_layout"
tools:itemCount="3"
tools:layout_editor_absoluteX="-99dp"
tools:listitem="@layout/item_modal_space"
tools:visibility="visible" />
<View
android:id="@+id/header_bottom_shadow"
android:layout_width="match_parent"
android:layout_height="4dp"
android:background="@drawable/top_shadow"
android:visibility="gone"
android:rotation="180"
app:layout_constraintTop_toBottomOf="@id/header_text_layout" />
<androidx.constraintlayout.widget.Group
android:id="@+id/no_spaces_yet_group"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
app:constraint_referenced_ids="no_spaces_yet_text, no_spaces_yet_message" />
<TextView
android:id="@+id/no_spaces_yet_text"
style="@style/Widget.Vector.TextView.Title.Medium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_spaces_yet"
android:textSize="16sp"
app:layout_constraintTop_toBottomOf="@id/header_text_layout"
app:layout_constraintBottom_toTopOf="@id/no_spaces_yet_message"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/no_spaces_yet_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:layout_marginTop="12dp"
android:text="@string/no_spaces_yet_message"
android:textColor="?attr/vctr_content_tertiary"
app:layout_constraintTop_toBottomOf="@id/no_spaces_yet_text"
app:layout_constraintBottom_toTopOf="@id/add_space_layout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<View
android:id="@+id/add_space_top_shadow"
android:layout_width="match_parent"
android:layout_height="4dp"
android:background="@drawable/top_shadow"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/add_space_layout" />
<View
android:id="@+id/add_space_top_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/vctr_content_quinary"
app:layout_constraintBottom_toTopOf="@id/add_space_layout" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/add_space_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_modal_ripple_grey"
android:elevation="3dp"
app:layout_constraintBottom_toBottomOf="parent">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/add_space_icon_background"
android:layout_width="42dp"
android:layout_height="42dp"
android:layout_marginStart="16dp"
android:background="#4D8D97A5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:shapeAppearanceOverlay="@style/SpaceListModalImageShapeOverlay" />
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:importantForAccessibility="no"
android:src="@drawable/ic_plus"
app:layout_constraintBottom_toBottomOf="@id/add_space_icon_background"
app:layout_constraintEnd_toEndOf="@id/add_space_icon_background"
app:layout_constraintStart_toStartOf="@id/add_space_icon_background"
app:layout_constraintTop_toTopOf="@id/add_space_icon_background"
app:tint="?attr/vctr_spoiler_background_color" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/add_space_text"
style="@style/Widget.Vector.TextView.Body.Medium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="@string/add_space"
android:textSize="17sp"
android:textColor="?attr/vctr_message_text_color"
app:layout_constraintBottom_toBottomOf="@id/add_space_icon_background"
app:layout_constraintStart_toEndOf="@id/add_space_icon_background"
app:layout_constraintTop_toTopOf="@id/add_space_icon_background" />
<Space
android:layout_width="match_parent"
android:layout_height="16dp"
app:layout_constraintTop_toBottomOf="@id/add_space_icon_background" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -36,13 +36,13 @@
android:textColor="?vctr_content_primary"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@id/groupBottomSeparator"
app:layout_constraintEnd_toStartOf="@id/groupAvatarChevron"
app:layout_constraintEnd_toStartOf="@id/chevron"
app:layout_constraintStart_toEndOf="@id/groupAvatarImageView"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/lorem/random" />
<ImageView
android:id="@+id/groupAvatarChevron"
android:id="@+id/chevron"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="21dp"

View File

@ -0,0 +1,65 @@
<?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:background="@drawable/ripple_grey"
android:paddingTop="12dp"
android:paddingBottom="12dp">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/avatar"
android:layout_width="42dp"
android:layout_height="42dp"
android:layout_marginStart="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearanceOverlay="@style/SpaceListModalImageShapeOverlay"
tools:background="#42A5F5" />
<TextView
android:id="@+id/name"
style="@style/Widget.Vector.TextView.Body.Medium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:textSize="17sp"
app:layout_constraintBottom_toBottomOf="@id/avatar"
app:layout_constraintStart_toEndOf="@id/avatar"
app:layout_constraintTop_toTopOf="@id/avatar"
tools:text="Space name" />
<im.vector.app.features.home.room.list.UnreadCounterBadgeView
android:id="@+id/counter_badge"
style="@style/Widget.Vector.TextView.Micro"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:minWidth="16dp"
android:minHeight="16dp"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:textColor="?colorOnError"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="@id/chevron"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
tools:background="@drawable/bg_unread_highlight"
tools:text="147"
tools:visibility="visible" />
<ImageView
android:id="@+id/chevron"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
android:importantForAccessibility="no"
android:src="@drawable/ic_arrow_right"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="?vctr_content_primary"
tools:ignore="MissingPrefix" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -183,7 +183,7 @@
tools:text="@tools:sample/date/hhmm" />
<TextView
android:id="@+id/roomLastEventView"
android:id="@+id/subtitleView"
style="@style/Widget.Vector.TextView.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
@ -213,7 +213,8 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/roomNameView"
app:layout_constraintTop_toBottomOf="@id/roomNameView"
tools:text="Alice is typing…" />
tools:text="Alice is typing…"
tools:visibility="gone" />
<!-- Margin bottom does not work, so I use space -->
<Space
@ -221,7 +222,7 @@
android:layout_width="0dp"
android:layout_height="7dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/roomLastEventView"
app:layout_constraintTop_toBottomOf="@id/subtitleView"
tools:layout_marginStart="120dp" />
<androidx.constraintlayout.widget.Barrier

View File

@ -120,12 +120,12 @@
android:padding="4dp"
android:src="@drawable/ic_more_vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/groupAvatarChevron"
app:layout_constraintEnd_toStartOf="@id/chevron"
app:layout_constraintTop_toTopOf="parent"
app:tint="?vctr_content_secondary" />
<ImageView
android:id="@+id/groupAvatarChevron"
android:id="@+id/chevron"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="21dp"

View File

@ -101,12 +101,12 @@
android:padding="4dp"
android:src="@drawable/ic_more_vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/groupAvatarChevron"
app:layout_constraintEnd_toStartOf="@id/chevron"
app:layout_constraintTop_toTopOf="parent"
app:tint="?vctr_content_secondary" />
<ImageView
android:id="@+id/groupAvatarChevron"
android:id="@+id/chevron"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="21dp"

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:background="@drawable/ripple_grey"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/avatar"
android:layout_width="42dp"
android:layout_height="42dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@sample/space_avatars"
android:layout_marginStart="16dp" />
<androidx.appcompat.widget.AppCompatTextView
style="@style/Widget.Vector.TextView.Body.Medium"
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:textSize="16sp"
app:layout_constraintTop_toTopOf="@id/avatar"
app:layout_constraintBottom_toTopOf="@id/invited_by"
app:layout_constraintStart_toEndOf="@id/avatar"
app:layout_constraintVertical_chainStyle="spread_inside"
tools:text="Element Corp."/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/Widget.Vector.TextView.Body"
android:id="@+id/invited_by"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="?attr/vctr_content_secondary"
app:layout_constraintTop_toBottomOf="@id/name"
app:layout_constraintBottom_toBottomOf="@id/avatar"
app:layout_constraintStart_toStartOf="@id/name"
tools:text="Invited by John from Cornwall"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -7,26 +7,31 @@
android:id="@+id/menu_home_setting"
android:icon="@drawable/ic_settings_x"
android:title="@string/settings"
android:orderInCategory="100"
app:showAsAction="never" />
<item
android:id="@+id/menu_home_suggestion"
android:icon="@drawable/ic_material_bug_report"
android:orderInCategory="110"
android:title="@string/send_suggestion" />
<item
android:id="@+id/menu_home_report_bug"
android:icon="@drawable/ic_material_bug_report"
android:orderInCategory="120"
android:title="@string/send_bug_report" />
<item
android:id="@+id/menu_home_init_sync_legacy"
android:title="Do a legacy init sync"
android:orderInCategory="130"
tools:ignore="HardcodedText" />
<item
android:id="@+id/menu_home_init_sync_optimized"
android:title="Do an optimized init sync"
android:orderInCategory="140"
tools:ignore="HardcodedText" />
<item

View File

@ -1,9 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_home_mark_all_as_read"
android:icon="@drawable/ic_material_done"
android:orderInCategory="50"
android:title="@string/action_mark_all_as_read" />
</menu>
<item
android:id="@+id/menu_explore_rooms"
android:title="@string/space_explore_activity_title"
android:orderInCategory="10"
app:showAsAction="never" />
<item
android:id="@+id/menu_invite_people"
android:title="@string/invite_people_menu"
android:orderInCategory="20"
app:showAsAction="never" />
<item
android:id="@+id/menu_add_rooms"
android:title="@string/space_add_child_title"
android:orderInCategory="30"
app:showAsAction="never" />
</menu>

View File

@ -620,8 +620,6 @@
<string name="room_participants_leave_prompt_msg">Are you sure you want to leave the room?</string>
<string name="room_participants_leave_private_warning">This room is not public. You will not be able to rejoin without an invite.</string>
<string name="room_participants_header_direct_chats">Direct Messages</string>
<string name="room_participants_action_invite">Invite</string>
<string name="room_participants_action_cancel_invite">Cancel invite</string>
<string name="room_participants_action_ban">Ban</string>
@ -897,7 +895,6 @@
<string name="settings_messages_containing_display_name">My display name</string>
<string name="settings_messages_containing_username">My username</string>
<string name="settings_messages_direct_messages">Direct messages</string>
<string name="settings_encrypted_direct_messages">Encrypted direct messages</string>
<string name="settings_group_messages">Group messages</string>
<string name="settings_encrypted_group_messages">Encrypted group messages</string>
@ -1607,7 +1604,6 @@
<string name="room_preview_not_found">This room is not accessible at this time.\nTry again later, or ask a room admin to check if you have access.</string>
<string name="room_preview_no_preview_join">"This room can't be previewed. Do you want to join it?"</string>
<string name="fab_menu_create_room">"Rooms"</string>
<string name="fab_menu_create_chat">"Direct Messages"</string>
<!-- Create room screen -->
<string name="create_room_action_create">"CREATE"</string>
@ -1680,7 +1676,11 @@
<string name="settings_labs_show_hidden_events_in_timeline">Show hidden events in timeline</string>
<string name="settings_labs_show_complete_history_in_encrypted_room">"Show complete history in encrypted rooms"</string>
<string name="bottom_action_people_x">Direct Messages</string>
<string name="fab_menu_create_chat">"Direct Messages"</string>
<string name="room_participants_header_direct_chats">Direct Messages</string>
<string name="bottom_action_people_x">People</string>
<string name="settings_messages_direct_messages">Direct messages</string>
<string name="send_file_step_idle">Waiting…</string>
<string name="send_file_step_encrypting_thumbnail">Encrypting thumbnail…</string>
@ -3042,4 +3042,13 @@
<!-- Screen sharing -->
<string name="screen_sharing_notification_title">${app_name} Screen Sharing</string>
<string name="screen_sharing_notification_description">Screen sharing is in progress</string>
<string name="all_chats">All Chats</string>
<string name="choose_a_space">Spaces</string>
<string name="invites">Invites</string>
<string name="chats">Chats</string>
<string name="no_spaces_yet">No spaces yet</string>
<string name="no_spaces_yet_message">Add spaces to group your chats</string>
<string name="no_subspaces_yet">No subspaces yet</string>
<string name="no_subspaces_yet_message">Add subspaces to group your chats</string>
<string name="space_invites">Space Invites</string>
</resources>