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

View File

@ -104,7 +104,7 @@ allprojects {
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
// Warnings are potential errors, so stop ignoring them // Warnings are potential errors, so stop ignoring them
// You can override by passing `-PallWarningsAsErrors=false` in the command line // 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 // 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) --> <!-- Created to detect what has to be implemented (especially in the settings) -->
<bool name="false_not_implemented">false</bool> <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_status_bar_background_dark">@color/element_background_dark</color>
<color name="android_navigation_bar_background_dark">@color/element_system_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 --> <!-- Used for toolbar background -->
<attr name="vctr_toolbar_background" format="color" /> <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> <item name="android:textAppearance">@style/TextAppearance.Vector.Title.Medium</item>
</style> </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"> <style name="Widget.Vector.TextView.HeadlineMedium">
<item name="android:textAppearance">@style/TextAppearance.Vector.Headline.Medium</item> <item name="android:textAppearance">@style/TextAppearance.Vector.Headline.Medium</item>
</style> </style>

View File

@ -24,6 +24,11 @@
<item name="android:fontFamily">sans-serif-medium</item> <item name="android:fontFamily">sans-serif-medium</item>
</style> </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"> <style name="TextAppearance.Vector.Headline.Medium" parent="TextAppearance.MaterialComponents.Headline1">
<item name="fontFamily">sans-serif-medium</item> <item name="fontFamily">sans-serif-medium</item>
<item name="android: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( data class SpaceChildInfo(
val roomId: String, val roomId: String,
val order: String?, val order: String?,
// val autoJoin: Boolean,
val viaServers: List<String> val viaServers: List<String>
) )
@ -60,18 +59,13 @@ internal class RoomChildRelationInfo(
fun getDirectChildrenDescriptions(): List<SpaceChildInfo> { fun getDirectChildrenDescriptions(): List<SpaceChildInfo> {
return CurrentStateEventEntity.whereType(realm, roomId, EventType.STATE_SPACE_CHILD) return CurrentStateEventEntity.whereType(realm, roomId, EventType.STATE_SPACE_CHILD)
.findAll() .findAll()
// .also {
// Timber.v("## Space: Found ${it.count()} m.space.child state events for $roomId")
// }
.mapNotNull { .mapNotNull {
ContentMapper.map(it.root?.content).toModel<SpaceChildContent>()?.let { scc -> 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. // Children where via is not present are ignored.
scc.via?.let { via -> scc.via?.let { via ->
SpaceChildInfo( SpaceChildInfo(
roomId = it.stateKey, roomId = it.stateKey,
order = scc.validOrder(), order = scc.validOrder(),
// autoJoin = scc.autoJoin ?: false,
viaServers = via viaServers = via
) )
} }
@ -83,17 +77,13 @@ internal class RoomChildRelationInfo(
fun getParentDescriptions(): List<SpaceParentInfo> { fun getParentDescriptions(): List<SpaceParentInfo> {
return CurrentStateEventEntity.whereType(realm, roomId, EventType.STATE_SPACE_PARENT) return CurrentStateEventEntity.whereType(realm, roomId, EventType.STATE_SPACE_PARENT)
.findAll() .findAll()
// .also {
// Timber.v("## Space: Found ${it.count()} m.space.parent state events for $roomId")
// }
.mapNotNull { .mapNotNull {
ContentMapper.map(it.root?.content).toModel<SpaceParentContent>()?.let { scc -> ContentMapper.map(it.root?.content).toModel<SpaceParentContent>()?.let { spaceParentContent ->
// Timber.v("## Space parent desc state event $scc")
// Parent where via is not present are ignored. // Parent where via is not present are ignored.
scc.via?.let { via -> spaceParentContent.via?.let { via ->
SpaceParentInfo( SpaceParentInfo(
roomId = it.stateKey, roomId = it.stateKey,
canonical = scc.canonical ?: false, canonical = spaceParentContent.canonical ?: false,
viaServers = via, viaServers = via,
stateEventSender = it.root?.sender ?: "" 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.Membership
import org.matrix.android.sdk.api.session.room.model.RoomSummary 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.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.roomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.spaceSummaryQueryParams import org.matrix.android.sdk.api.session.room.spaceSummaryQueryParams
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
@ -193,6 +194,18 @@ internal class RoomSummaryDataSource @Inject constructor(
} }
val dataSourceFactory = realmDataSourceFactory.map { val dataSourceFactory = realmDataSourceFactory.map {
roomSummaryMapper.map(it) 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()) 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. // 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 // When creating a hotfix, you should decrease the value, since the current value
// is the value for the next regular release. // is the value for the next regular release.
ext.versionPatch = 16 ext.versionPatch = 17
static def getGitTimestamp() { static def getGitTimestamp() {
def cmd = 'git show -s --format=%ct' def cmd = 'git show -s --format=%ct'
@ -262,7 +262,7 @@ android {
dimension "store" dimension "store"
isDefault = true isDefault = true
versionName "${versionMajor}.${versionMinor}.${versionPatch}${getGplayVersionSuffix()}" versionName "${versionMajor}.${versionMinor}.${versionPatch}-experiment-spaceSwitching"
resValue "bool", "isGplay", "true" resValue "bool", "isGplay", "true"
buildConfigField "boolean", "ALLOW_FCM_USE", "true" buildConfigField "boolean", "ALLOW_FCM_USE", "true"
@ -273,7 +273,7 @@ android {
fdroid { fdroid {
dimension "store" dimension "store"
versionName "${versionMajor}.${versionMinor}.${versionPatch}${getFdroidVersionSuffix()}" versionName "${versionMajor}.${versionMinor}.${versionPatch}-experiment-spaceSwitching"
resValue "bool", "isGplay", "false" resValue "bool", "isGplay", "false"
buildConfigField "boolean", "ALLOW_FCM_USE", "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.airbnb.mvrx.MavericksView
import com.bumptech.glide.util.Util import com.bumptech.glide.util.Util
import com.google.android.material.appbar.MaterialToolbar 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.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.EntryPointAccessors import dagger.hilt.android.EntryPointAccessors
@ -238,6 +239,14 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
initUiAndData() 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() val titleRes = getTitleRes()
if (titleRes != -1) { if (titleRes != -1) {
supportActionBar?.let { 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 package im.vector.app.features.home
import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.net.Uri 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) { // fun renderSpace(matrixItem: MatrixItem, imageView: ImageView) {
// renderSpace( // renderSpace(
// matrixItem, // matrixItem,

View File

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

View File

@ -19,11 +19,14 @@ package im.vector.app.features.home
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.DecelerateInterpolator
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState 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.AppStateHandler
import im.vector.app.R import im.vector.app.R
import im.vector.app.RoomGroupingMethod import im.vector.app.RoomGroupingMethod
import im.vector.app.SessionVariables
import im.vector.app.core.extensions.commitTransaction import im.vector.app.core.extensions.commitTransaction
import im.vector.app.core.extensions.toMvRxBundle import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.core.platform.OnBackPressed 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.CurrentCallsViewPresenter
import im.vector.app.core.ui.views.KeysBackupBanner import im.vector.app.core.ui.views.KeysBackupBanner
import im.vector.app.databinding.FragmentHomeDetailBinding 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.SharedKnownCallsViewModel
import im.vector.app.features.call.VectorCallActivity import im.vector.app.features.call.VectorCallActivity
import im.vector.app.features.call.dialpad.DialPadFragment 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.VectorLocale
import im.vector.app.features.settings.VectorPreferences 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.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.themes.ThemeUtils
import im.vector.app.features.workers.signout.BannerState import im.vector.app.features.workers.signout.BannerState
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel 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.crypto.model.DeviceInfo
import org.matrix.android.sdk.api.session.group.model.GroupSummary 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.session.room.model.RoomSummary
import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject import javax.inject.Inject
class HomeDetailFragment @Inject constructor( class HomeDetailFragment @Inject constructor(
@ -81,6 +92,8 @@ class HomeDetailFragment @Inject constructor(
private lateinit var sharedActionViewModel: HomeSharedActionViewModel private lateinit var sharedActionViewModel: HomeSharedActionViewModel
private lateinit var sharedCallActionViewModel: SharedKnownCallsViewModel private lateinit var sharedCallActionViewModel: SharedKnownCallsViewModel
private var isInSpace = false
private var hasUnreadRooms = false private var hasUnreadRooms = false
set(value) { set(value) {
if (value != field) { if (value != field) {
@ -97,16 +110,39 @@ class HomeDetailFragment @Inject constructor(
viewModel.handle(HomeDetailAction.MarkAllRoomsRead) viewModel.handle(HomeDetailAction.MarkAllRoomsRead)
return true 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) 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) { override fun onPrepareOptionsMenu(menu: Menu) {
withState(viewModel) { state -> withState(viewModel) { state ->
val isRoomList = state.currentTab is HomeTab.RoomList 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) super.onPrepareOptionsMenu(menu)
} }
@ -132,8 +168,13 @@ class HomeDetailFragment @Inject constructor(
viewModel.onEach(HomeDetailViewState::roomGroupingMethod) { roomGroupingMethod -> viewModel.onEach(HomeDetailViewState::roomGroupingMethod) { roomGroupingMethod ->
when (roomGroupingMethod) { when (roomGroupingMethod) {
is RoomGroupingMethod.ByLegacyGroup -> onGroupChange(roomGroupingMethod.groupSummary) is RoomGroupingMethod.ByLegacyGroup -> {
is RoomGroupingMethod.BySpace -> onSpaceChange(roomGroupingMethod.spaceSummary) onGroupChange(roomGroupingMethod.groupSummary)
}
is RoomGroupingMethod.BySpace -> {
onSpaceChange(roomGroupingMethod.spaceSummary)
sharedActionViewModel.space.value = roomGroupingMethod.spaceSummary
}
} }
} }
@ -141,12 +182,40 @@ class HomeDetailFragment @Inject constructor(
updateUIForTab(currentTab) updateUIForTab(currentTab)
} }
viewModel.onEach(HomeDetailViewState::showDialPadTab) { showDialPadTab -> views.allChatsLayout.setOnClickListener {
updateTabVisibilitySafely(R.id.bottom_action_dial_pad, showDialPadTab) toggleModalVisibility()
} }
views.groupToolbarNavigateUp.setOnClickListener { views.backButtonLayout.setOnClickListener {
navigateUpOneSpace() 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 -> viewModel.observeViewEvents { viewEvent ->
@ -189,6 +258,57 @@ class HomeDetailFragment @Inject constructor(
currentCallsViewPresenter.updateCall(callManager.getCurrentCall(), callManager.getCalls()) currentCallsViewPresenter.updateCall(callManager.getCurrentCall(), callManager.getCalls())
invalidateOptionsMenu() 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() { private fun navigateUpOneSpace() {
@ -283,24 +403,45 @@ class HomeDetailFragment @Inject constructor(
} }
private fun onGroupChange(groupSummary: GroupSummary?) { private fun onGroupChange(groupSummary: GroupSummary?) {
hideModal()
if (groupSummary == null) { if (groupSummary == null) {
views.backButtonLayout.isVisible = false
views.groupToolbarSpaceTitleView.isVisible = false views.groupToolbarSpaceTitleView.isVisible = false
views.groupToolbarSpaceTitleView.text = getString(R.string.all_chats)
views.groupToolbarTitleView.text = getString(R.string.all_chats)
} else { } else {
views.backButtonLayout.isVisible = true
views.groupToolbarSpaceTitleView.isVisible = true views.groupToolbarSpaceTitleView.isVisible = true
views.groupToolbarSpaceTitleView.text = groupSummary.displayName views.groupToolbarSpaceTitleView.text = groupSummary.displayName
views.groupToolbarTitleView.text = groupSummary.displayName
} }
} }
private fun onSpaceChange(spaceSummary: RoomSummary?) { private fun onSpaceChange(spaceSummary: RoomSummary?) {
hideModal()
views.backButtonText.text = getString(R.string.all_chats)
views.toolbarChevron.rotation = 0F
if (spaceSummary == null) { if (spaceSummary == null) {
isInSpace = false
invalidateOptionsMenu()
views.backButtonLayout.isVisible = false
views.groupToolbarSpaceTitleView.isVisible = false views.groupToolbarSpaceTitleView.isVisible = false
views.groupToolbarAvatarImageView.isVisible = true views.groupToolbarSpaceTitleView.text = getString(R.string.all_chats)
views.groupToolbarNavigateUp.isVisible = false views.groupToolbarTitleView.text = getString(R.string.all_chats)
views.spaceAvatar.isVisible = false
} else { } else {
isInSpace = true
invalidateOptionsMenu()
views.backButtonLayout.isVisible = true
views.groupToolbarSpaceTitleView.isVisible = true views.groupToolbarSpaceTitleView.isVisible = true
views.groupToolbarSpaceTitleView.text = spaceSummary.displayName views.groupToolbarSpaceTitleView.text = spaceSummary.displayName
views.groupToolbarAvatarImageView.isVisible = false views.groupToolbarTitleView.text = spaceSummary.displayName
views.groupToolbarNavigateUp.isVisible = true 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) sharedActionViewModel.post(HomeActivitySharedAction.OpenDrawer)
} }
views.homeToolbarContent.debouncedClicks { // views.homeToolbarContent.debouncedClicks {
withState(viewModel) { // withState(viewModel) {
when (it.roomGroupingMethod) { // when (it.roomGroupingMethod) {
is RoomGroupingMethod.ByLegacyGroup -> { // is RoomGroupingMethod.ByLegacyGroup -> {
// do nothing // // do nothing
} // }
is RoomGroupingMethod.BySpace -> { // is RoomGroupingMethod.BySpace -> {
it.roomGroupingMethod.spaceSummary?.let { spaceSummary -> // it.roomGroupingMethod.spaceSummary?.let { spaceSummary ->
sharedActionViewModel.post(HomeActivitySharedAction.ShowSpaceSettings(spaceSummary.roomId)) // sharedActionViewModel.post(HomeActivitySharedAction.ShowSpaceSettings(spaceSummary.roomId))
} // }
} // }
} // }
} // }
} // }
} }
private fun setupBottomNavigationView() { private fun setupBottomNavigationView() {
@ -361,7 +502,6 @@ class HomeDetailFragment @Inject constructor(
private fun updateUIForTab(tab: HomeTab) { private fun updateUIForTab(tab: HomeTab) {
views.bottomNavigationView.menu.findItem(tab.toMenuId()).isChecked = true views.bottomNavigationView.menu.findItem(tab.toMenuId()).isChecked = true
views.groupToolbarTitleView.setText(tab.titleRes)
updateSelectedFragment(tab) updateSelectedFragment(tab)
invalidateOptionsMenu() invalidateOptionsMenu()
} }
@ -375,8 +515,10 @@ class HomeDetailFragment @Inject constructor(
childFragmentManager.fragments childFragmentManager.fragments
.filter { it != fragmentToShow } .filter { it != fragmentToShow }
.forEach { .forEach {
if (it.id != R.id.space_modal_fragment) {
detach(it) detach(it)
} }
}
if (fragmentToShow == null) { if (fragmentToShow == null) {
when (tab) { when (tab) {
is HomeTab.RoomList -> { is HomeTab.RoomList -> {

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.extensions.singletonEntryPoint
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.VectorOverrides 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.dialpad.DialPadLookup
import im.vector.app.features.call.lookup.CallProtocolsChecker import im.vector.app.features.call.lookup.CallProtocolsChecker
import im.vector.app.features.call.webrtc.WebRtcCallManager 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.AutoAcceptInvites
import im.vector.app.features.invite.showInvites import im.vector.app.features.invite.showInvites
import im.vector.app.features.settings.VectorDataStore 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.features.ui.UiStateRepository
import im.vector.app.space
import im.vector.lib.core.utils.flow.throttleFirst import im.vector.lib.core.utils.flow.throttleFirst
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.distinctUntilChanged 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.initsync.SyncStatusService
import org.matrix.android.sdk.api.session.room.RoomSortOrder 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.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.roomSummaryQueryParams
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.flow
@ -70,7 +78,8 @@ class HomeDetailViewModel @AssistedInject constructor(
private val directRoomHelper: DirectRoomHelper, private val directRoomHelper: DirectRoomHelper,
private val appStateHandler: AppStateHandler, private val appStateHandler: AppStateHandler,
private val autoAcceptInvites: AutoAcceptInvites, private val autoAcceptInvites: AutoAcceptInvites,
private val vectorOverrides: VectorOverrides private val vectorOverrides: VectorOverrides,
private val analyticsTracker: AnalyticsTracker
) : VectorViewModel<HomeDetailViewState, HomeDetailAction, HomeDetailViewEvents>(initialState), ) : VectorViewModel<HomeDetailViewState, HomeDetailAction, HomeDetailViewEvents>(initialState),
CallProtocolsChecker.Listener { 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() { private fun observeRoomSummaries() {
appStateHandler.selectedRoomGroupingFlow.distinctUntilChanged().flatMapLatest { appStateHandler.selectedRoomGroupingFlow.distinctUntilChanged().flatMapLatest {
trackSpaceSwitch()
// we use it as a trigger to all changes in room, but do not really load // we use it as a trigger to all changes in room, but do not really load
// the actual models // the actual models
session.roomService().getPagedRoomSummariesLive( session.roomService().getPagedRoomSummariesLive(

View File

@ -16,8 +16,13 @@
package im.vector.app.features.home package im.vector.app.features.home
import androidx.lifecycle.MutableLiveData
import im.vector.app.core.platform.VectorSharedActionViewModel import im.vector.app.core.platform.VectorSharedActionViewModel
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import javax.inject.Inject 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() { override fun buildModels() {
check(isBuildingModels()) { check(isBuildingModels) {
("You cannot call `buildModels` directly. Call `setData` instead to trigger a model " + ("You cannot call `buildModels` directly. Call `setData` instead to trigger a model " +
"refresh with new data.") "refresh with new data.")
} }

View File

@ -211,7 +211,7 @@ class RoomListFragment @Inject constructor(
RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.isVisible = true RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.isVisible = true
RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.isVisible = true RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.isVisible = true
RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.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 { views.createChatRoomButton.debouncedClicks {
@ -237,7 +237,7 @@ class RoomListFragment @Inject constructor(
RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.hide() RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.hide()
RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.hide() RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.hide()
RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.hide() RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.hide()
else -> Unit RoomListDisplayMode.FILTERED -> Unit
} }
} }
} }
@ -294,7 +294,7 @@ class RoomListFragment @Inject constructor(
val contentAdapter = val contentAdapter =
when { when {
section.livePages != null -> { section.livePages != null -> {
pagedControllerFactory.createRoomSummaryPagedController() pagedControllerFactory.createRoomSummaryPagedController(roomListParams.displayMode)
.also { controller -> .also { controller ->
section.livePages.observe(viewLifecycleOwner) { pl -> section.livePages.observe(viewLifecycleOwner) { pl ->
controller.submitList(pl) controller.submitList(pl)
@ -316,7 +316,7 @@ class RoomListFragment @Inject constructor(
) )
} }
} }
section.isExpanded.observe(viewLifecycleOwner) { _ -> section.isExpanded.observe(viewLifecycleOwner) {
refreshCollapseStates() refreshCollapseStates()
} }
controller.listener = this controller.listener = this
@ -337,14 +337,14 @@ class RoomListFragment @Inject constructor(
checkEmptyState() checkEmptyState()
} }
observeItemCount(section, sectionAdapter) observeItemCount(section, sectionAdapter)
section.isExpanded.observe(viewLifecycleOwner) { _ -> section.isExpanded.observe(viewLifecycleOwner) {
refreshCollapseStates() refreshCollapseStates()
} }
controller.listener = this controller.listener = this
} }
} }
else -> { else -> {
pagedControllerFactory.createRoomSummaryListController() pagedControllerFactory.createRoomSummaryListController(roomListParams.displayMode)
.also { controller -> .also { controller ->
section.liveList?.observe(viewLifecycleOwner) { list -> section.liveList?.observe(viewLifecycleOwner) { list ->
controller.setData(list) controller.setData(list)
@ -366,7 +366,7 @@ class RoomListFragment @Inject constructor(
) )
} }
} }
section.isExpanded.observe(viewLifecycleOwner) { _ -> section.isExpanded.observe(viewLifecycleOwner) {
refreshCollapseStates() refreshCollapseStates()
} }
controller.listener = this controller.listener = this
@ -402,7 +402,7 @@ class RoomListFragment @Inject constructor(
RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.show() RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.show()
RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.show() RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.show()
RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.show() RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.show()
else -> Unit RoomListDisplayMode.FILTERED -> Unit
} }
} }
} }
@ -498,7 +498,7 @@ class RoomListFragment @Inject constructor(
isBigImage = true, isBigImage = true,
message = getString(R.string.room_list_rooms_empty_body) message = getString(R.string.room_list_rooms_empty_body)
) )
else -> RoomListDisplayMode.FILTERED ->
// Always display the content in this mode, because if the footer // Always display the content in this mode, because if the footer
StateView.State.Content 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.core.ui.views.ShieldImageView
import im.vector.app.features.displayname.getBestName import im.vector.app.features.displayname.getBestName
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeUtils
import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel 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) @EpoxyModelClass(layout = R.layout.item_room)
abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() { abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
@EpoxyAttribute lateinit var typingMessage: String @EpoxyAttribute
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer lateinit var typingMessage: String
@EpoxyAttribute lateinit var matrixItem: MatrixItem
@EpoxyAttribute lateinit var lastFormattedEvent: EpoxyCharSequence @EpoxyAttribute
@EpoxyAttribute lateinit var lastEventTime: String lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute var encryptionTrustLevel: RoomEncryptionTrustLevel? = null
@EpoxyAttribute var userPresence: UserPresence? = null @EpoxyAttribute
@EpoxyAttribute var showPresence: Boolean = false lateinit var matrixItem: MatrixItem
@EpoxyAttribute var izPublic: Boolean = false
@EpoxyAttribute var unreadNotificationCount: Int = 0 @EpoxyAttribute
@EpoxyAttribute var hasUnreadMessage: Boolean = false var displayMode: RoomListDisplayMode = RoomListDisplayMode.PEOPLE
@EpoxyAttribute var hasDraft: Boolean = false
@EpoxyAttribute var showHighlighted: Boolean = false @EpoxyAttribute
@EpoxyAttribute var hasFailedSending: Boolean = false lateinit var subtitle: String
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemLongClickListener: View.OnLongClickListener? = null
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: ClickListener? = null @EpoxyAttribute
@EpoxyAttribute var showSelected: Boolean = false 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) { override fun bind(holder: Holder) {
super.bind(holder) super.bind(holder)
renderDisplayMode(holder)
holder.rootView.onClick(itemClickListener) holder.rootView.onClick(itemClickListener)
holder.rootView.setOnLongClickListener { holder.rootView.setOnLongClickListener {
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
itemLongClickListener?.onLongClick(it) ?: false itemLongClickListener?.onLongClick(it) ?: false
} }
holder.titleView.text = matrixItem.getBestName() holder.titleView.text = matrixItem.getBestName()
holder.lastEventTimeView.text = lastEventTime
holder.lastEventView.text = lastFormattedEvent.charSequence
holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted)) holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted))
holder.unreadIndentIndicator.isVisible = hasUnreadMessage holder.unreadIndentIndicator.isVisible = hasUnreadMessage
holder.draftView.isVisible = hasDraft holder.draftView.isVisible = hasDraft
avatarRenderer.render(matrixItem, holder.avatarImageView) avatarRenderer.render(matrixItem, holder.avatarImageView)
holder.roomAvatarDecorationImageView.render(encryptionTrustLevel) holder.roomAvatarDecorationImageView.render(encryptionTrustLevel)
holder.roomAvatarPublicDecorationImageView.isVisible = izPublic holder.roomAvatarPublicDecorationImageView.isVisible = isPublic
holder.roomAvatarFailSendingImageView.isVisible = hasFailedSending holder.roomAvatarFailSendingImageView.isVisible = hasFailedSending
renderSelection(holder, showSelected) renderSelection(holder, showSelected)
holder.typingView.setTextOrHide(typingMessage)
holder.lastEventView.isInvisible = holder.typingView.isVisible
holder.roomAvatarPresenceImageView.render(showPresence, userPresence) 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) { override fun unbind(holder: Holder) {
holder.rootView.setOnClickListener(null) holder.rootView.setOnClickListener(null)
holder.rootView.setOnLongClickListener(null) holder.rootView.setOnLongClickListener(null)
@ -110,7 +165,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
val titleView by bind<TextView>(R.id.roomNameView) val titleView by bind<TextView>(R.id.roomNameView)
val unreadCounterBadgeView by bind<UnreadCounterBadgeView>(R.id.roomUnreadCounterBadgeView) val unreadCounterBadgeView by bind<UnreadCounterBadgeView>(R.id.roomUnreadCounterBadgeView)
val unreadIndentIndicator by bind<View>(R.id.roomUnreadIndicator) 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 typingView by bind<TextView>(R.id.roomTypingView)
val draftView by bind<ImageView>(R.id.roomDraftBadge) val draftView by bind<ImageView>(R.id.roomDraftBadge)
val lastEventTimeView by bind<TextView>(R.id.roomLastEventTimeView) 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.error.ErrorFormatter
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.home.AvatarRenderer 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.detail.timeline.format.DisplayableEventFormatter
import im.vector.app.features.home.room.typing.TypingHelper import im.vector.app.features.home.room.typing.TypingHelper
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
@ -46,13 +47,16 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
fun create(roomSummary: RoomSummary, fun create(roomSummary: RoomSummary,
roomChangeMembershipStates: Map<String, ChangeMembershipState>, roomChangeMembershipStates: Map<String, ChangeMembershipState>,
selectedRoomIds: Set<String>, selectedRoomIds: Set<String>,
displayMode: RoomListDisplayMode,
listener: RoomListListener?): VectorEpoxyModel<*> { listener: RoomListListener?): VectorEpoxyModel<*> {
return when (roomSummary.membership) { return when (roomSummary.membership) {
Membership.INVITE -> { Membership.INVITE -> {
val changeMembershipState = roomChangeMembershipStates[roomSummary.roomId] ?: ChangeMembershipState.Unknown val changeMembershipState = roomChangeMembershipStates[roomSummary.roomId] ?: ChangeMembershipState.Unknown
createInvitationItem(roomSummary, changeMembershipState, listener) 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( fun createRoomItem(
roomSummary: RoomSummary, roomSummary: RoomSummary,
selectedRoomIds: Set<String>, selectedRoomIds: Set<String>,
displayMode: RoomListDisplayMode,
onClick: ((RoomSummary) -> Unit)?, onClick: ((RoomSummary) -> Unit)?,
onLongClick: ((RoomSummary) -> Boolean)? onLongClick: ((RoomSummary) -> Boolean)?
): VectorEpoxyModel<*> { ): VectorEpoxyModel<*> {
val subtitle = getSearchResultSubtitle(roomSummary)
val unreadCount = roomSummary.notificationCount val unreadCount = roomSummary.notificationCount
val showHighlighted = roomSummary.highlightCount > 0 val showHighlighted = roomSummary.highlightCount > 0
val showSelected = selectedRoomIds.contains(roomSummary.roomId) 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()) latestFormattedEvent = displayableEventFormatter.format(latestEvent, roomSummary.isDirect, roomSummary.isDirect.not())
latestEventTime = dateFormatter.format(latestEvent.root.originServerTs, DateFormatKind.ROOM_LIST) latestEventTime = dateFormatter.format(latestEvent.root.originServerTs, DateFormatKind.ROOM_LIST)
} }
val typingMessage = typingHelper.getTypingMessage(roomSummary.typingUsers) val typingMessage = typingHelper.getTypingMessage(roomSummary.typingUsers)
return RoomSummaryItem_() return RoomSummaryItem_()
.id(roomSummary.roomId) .id(roomSummary.roomId)
.avatarRenderer(avatarRenderer) .avatarRenderer(avatarRenderer)
// We do not display shield in the room list anymore // We do not display shield in the room list anymore
// .encryptionTrustLevel(roomSummary.roomEncryptionTrustLevel) // .encryptionTrustLevel(roomSummary.roomEncryptionTrustLevel)
.izPublic(roomSummary.isPublic) .displayMode(displayMode)
.subtitle(subtitle)
.isPublic(roomSummary.isPublic)
.showPresence(roomSummary.isDirect) .showPresence(roomSummary.isDirect)
.userPresence(roomSummary.directUserPresence) .userPresence(roomSummary.directUserPresence)
.matrixItem(roomSummary.toMatrixItem()) .matrixItem(roomSummary.toMatrixItem())
@ -142,4 +151,12 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
} }
.itemClickListener { onClick?.invoke(roomSummary) } .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 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 import org.matrix.android.sdk.api.session.room.model.RoomSummary
class RoomSummaryListController( class RoomSummaryListController(
private val roomSummaryItemFactory: RoomSummaryItemFactory private val roomSummaryItemFactory: RoomSummaryItemFactory,
private val displayMode: RoomListDisplayMode
) : CollapsableTypedEpoxyController<List<RoomSummary>>() { ) : CollapsableTypedEpoxyController<List<RoomSummary>>() {
var listener: RoomListListener? = null var listener: RoomListListener? = null
override fun buildModels(data: List<RoomSummary>?) { override fun buildModels(data: List<RoomSummary>?) {
data?.forEach { 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.EpoxyModel
import com.airbnb.epoxy.paging.PagedListEpoxyController import com.airbnb.epoxy.paging.PagedListEpoxyController
import im.vector.app.core.utils.createUIHandler 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.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
class RoomSummaryPagedController( class RoomSummaryPagedController(
private val roomSummaryItemFactory: RoomSummaryItemFactory private val roomSummaryItemFactory: RoomSummaryItemFactory,
private val displayMode: RoomListDisplayMode
) : PagedListEpoxyController<RoomSummary>( ) : PagedListEpoxyController<RoomSummary>(
// Important it must match the PageList builder notify Looper // Important it must match the PageList builder notify Looper
modelBuildingHandler = createUIHandler() modelBuildingHandler = createUIHandler()
@ -57,6 +59,6 @@ class RoomSummaryPagedController(
override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> { override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> {
// for place holder if enabled // for place holder if enabled
item ?: return RoomSummaryItemPlaceHolder_().apply { id(currentPosition) } 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 package im.vector.app.features.home.room.list
import im.vector.app.features.home.RoomListDisplayMode
import javax.inject.Inject import javax.inject.Inject
class RoomSummaryPagedControllerFactory @Inject constructor( class RoomSummaryPagedControllerFactory @Inject constructor(
private val roomSummaryItemFactory: RoomSummaryItemFactory private val roomSummaryItemFactory: RoomSummaryItemFactory
) { ) {
fun createRoomSummaryPagedController(): RoomSummaryPagedController { fun createRoomSummaryPagedController(displayMode: RoomListDisplayMode): RoomSummaryPagedController {
return RoomSummaryPagedController(roomSummaryItemFactory) return RoomSummaryPagedController(roomSummaryItemFactory, displayMode)
} }
fun createRoomSummaryListController(): RoomSummaryListController { fun createRoomSummaryListController(displayMode: RoomListDisplayMode): RoomSummaryListController {
return RoomSummaryListController(roomSummaryItemFactory) return RoomSummaryListController(roomSummaryItemFactory, displayMode)
} }
fun createSuggestedRoomListController(): SuggestedRoomListController { 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.loadingItem
import im.vector.app.core.epoxy.noResultItem import im.vector.app.core.epoxy.noResultItem
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.home.room.list.RoomSummaryItemFactory import im.vector.app.features.home.room.list.RoomSummaryItemFactory
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
import javax.inject.Inject import javax.inject.Inject
@ -53,7 +54,13 @@ class IncomingShareController @Inject constructor(private val roomSummaryItemFac
} else { } else {
roomSummaries.forEach { roomSummary -> roomSummaries.forEach { roomSummary ->
roomSummaryItemFactory 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) .addTo(this)
} }
} }

View File

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

View File

@ -17,6 +17,7 @@
package im.vector.app.features.spaces package im.vector.app.features.spaces
import androidx.lifecycle.asFlow import androidx.lifecycle.asFlow
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.Success 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.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.analytics.AnalyticsTracker 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.invite.AutoAcceptInvites
import im.vector.app.features.session.coroutineScope import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorPreferences 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.toContent
import org.matrix.android.sdk.api.session.events.model.toModel 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.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.group.groupSummaryQueryParams
import org.matrix.android.sdk.api.session.room.RoomSortOrder 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.accountdata.RoomAccountDataTypes
import org.matrix.android.sdk.api.session.room.model.Membership 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.roomSummaryQueryParams
import org.matrix.android.sdk.api.session.room.spaceSummaryQueryParams import org.matrix.android.sdk.api.session.room.spaceSummaryQueryParams
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount 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 private val analyticsTracker: AnalyticsTracker
) : VectorViewModel<SpaceListViewState, SpaceListAction, SpaceListViewEvents>(initialState) { ) : VectorViewModel<SpaceListViewState, SpaceListAction, SpaceListViewEvents>(initialState) {
private var currentSpace: RoomSummary? = null
@AssistedFactory @AssistedFactory
interface Factory : MavericksAssistedViewModelFactory<SpaceListViewModel, SpaceListViewState> { interface Factory : MavericksAssistedViewModelFactory<SpaceListViewModel, SpaceListViewState> {
override fun create(initialState: SpaceListViewState): SpaceListViewModel override fun create(initialState: SpaceListViewState): SpaceListViewModel
@ -138,7 +143,7 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa
) )
setState { setState {
copy( copy(
homeAggregateCount = counts homeAggregateCount = counts,
) )
} }
} }
@ -229,14 +234,13 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa
private fun handleSelectSpace(action: SpaceListAction.SelectSpace) = withState { state -> private fun handleSelectSpace(action: SpaceListAction.SelectSpace) = withState { state ->
val groupingMethod = state.selectedGroupingMethod val groupingMethod = state.selectedGroupingMethod
val isAtSpace = groupingMethod.space() != null
if (groupingMethod is RoomGroupingMethod.ByLegacyGroup || groupingMethod.space()?.roomId != action.spaceSummary?.roomId) { 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)) } setState { copy(selectedGroupingMethod = RoomGroupingMethod.BySpace(action.spaceSummary)) }
appStateHandler.setCurrentSpace(action.spaceSummary?.roomId) appStateHandler.setCurrentSpace(action.spaceSummary?.roomId)
_viewEvents.post(SpaceListViewEvents.OpenSpace(groupingMethod is RoomGroupingMethod.ByLegacyGroup)) _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 -> private fun handleSelectGroup(action: SpaceListAction.SelectLegacyGroup) = withState { state ->
@ -273,24 +277,28 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa
_viewEvents.post(SpaceListViewEvents.AddSpace) _viewEvents.post(SpaceListViewEvents.AddSpace)
} }
var asyncSpaceList: Async<List<RoomSummary>>? = null
private fun observeSpaceSummaries() { private fun observeSpaceSummaries() {
val params = spaceSummaryQueryParams { val params = spaceSummaryQueryParams {
memberships = listOf(Membership.JOIN, Membership.INVITE) memberships = listOf(Membership.JOIN, Membership.INVITE)
displayName = QueryStringValue.IsNotEmpty displayName = QueryStringValue.IsNotEmpty
} }
combine( combine(session.flow().liveSpaceSummaries(params),
session.flow()
.liveSpaceSummaries(params),
session.accountDataService() session.accountDataService()
.getLiveRoomAccountDataEvents(setOf(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER)) .getLiveRoomAccountDataEvents(setOf(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER))
.asFlow() .asFlow()
) { spaces, _ -> ) { spaces, _ -> spaces }.execute { async ->
spaces asyncSpaceList = async
} val currentSpaceChildren = currentSpace?.let { space -> async.invoke()?.filter { it.flattenParentIds.contains(space.roomId) } }
.execute { async ->
val rootSpaces = async.invoke().orEmpty().filter { it.flattenParentIds.isEmpty() } val rootSpaces = async.invoke().orEmpty().filter { it.flattenParentIds.isEmpty() }
val orders = rootSpaces.associate { 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) it.roomId to session.getRoom(it.roomId)
?.roomAccountDataService() ?.roomAccountDataService()
?.getAccountDataEvent(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER) ?.getAccountDataEvent(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER)
@ -299,7 +307,10 @@ class SpaceListViewModel @AssistedInject constructor(@Assisted initialState: Spa
} }
copy( copy(
asyncSpaces = async, asyncSpaces = async,
rootSpacesOrdered = rootSpaces.sortedWith(TopLevelSpaceComparator(orders)), rootSpacesOrdered = displaySpaces.sortedWith(TopLevelSpaceComparator(orders)),
inviteSpaces = inviteSpaces.sortedWith(TopLevelSpaceComparator(orders)),
inviteCount = inviteSpaces.size,
inviteUserTask = inviteUserTask,
spaceOrderInfo = orders spaceOrderInfo = orders
) )
} }
@ -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 asyncSpaces: Async<List<RoomSummary>> = Uninitialized,
val selectedGroupingMethod: RoomGroupingMethod = RoomGroupingMethod.BySpace(null), val selectedGroupingMethod: RoomGroupingMethod = RoomGroupingMethod.BySpace(null),
val rootSpacesOrdered: List<RoomSummary>? = null, val rootSpacesOrdered: List<RoomSummary>? = null,
val inviteSpaces: List<RoomSummary>? = null,
val spaceOrderInfo: Map<String, String?>? = null, val spaceOrderInfo: Map<String, String?>? = null,
val spaceOrderLocalEchos: Map<String, String?>? = null, val spaceOrderLocalEchos: Map<String, String?>? = null,
val legacyGroups: List<GroupSummary>? = null, val legacyGroups: List<GroupSummary>? = null,
val expandedStates: Map<String, Boolean> = emptyMap(), val expandedStates: Map<String, Boolean> = emptyMap(),
val inviteCount: Int = 0,
val inviteUserTask: ((String) -> String?)? = null,
val homeAggregateCount: RoomAggregateNotificationCount = RoomAggregateNotificationCount(0, 0) val homeAggregateCount: RoomAggregateNotificationCount = RoomAggregateNotificationCount(0, 0)
) : MavericksState ) : 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.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:id="@+id/appBarLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:elevation="0dp"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
<im.vector.app.core.ui.views.CurrentCallsView <im.vector.app.core.ui.views.CurrentCallsView
@ -18,19 +19,64 @@
android:minHeight="48dp" android:minHeight="48dp"
android:visibility="gone" android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/homeKeysBackupBanner" app:layout_constraintTop_toBottomOf="@id/homeKeysBackupBanner"
tools:visibility="visible" /> tools:visibility="gone" />
<com.google.android.material.appbar.MaterialToolbar <com.google.android.material.appbar.MaterialToolbar
android:id="@+id/groupToolbar" android:id="@+id/groupToolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"> android:layout_height="wrap_content">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:baselineAligned="false" android:baselineAligned="false"
android:gravity="center_vertical" 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 <RelativeLayout
android:id="@+id/groupToolbarAvatarImageView" android:id="@+id/groupToolbarAvatarImageView"
@ -39,6 +85,7 @@
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:contentDescription="@string/a11y_open_drawer" android:contentDescription="@string/a11y_open_drawer"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
@ -74,58 +121,83 @@
</RelativeLayout> </RelativeLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/back_button_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView <ImageView
android:id="@+id/groupToolbarNavigateUp" android:id="@+id/back_button_chevron"
android:layout_width="28dp" android:layout_width="20dp"
android:layout_height="28dp" android:layout_height="20dp"
android:src="@drawable/ic_arrow_back" android:importantForAccessibility="no"
android:layout_marginEnd="8dp" android:src="@drawable/ic_back_24dp"
android:contentDescription="@string/a11y_navigate_up_space" android:layout_marginTop="16dp"
android:visibility="gone"
app:tint="?vctr_content_secondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:ignore="MissingPrefix" /> app:tint="?attr/vctr_message_text_color" />
<LinearLayout <TextView
android:id="@+id/homeToolbarContent" android:id="@+id/back_button_text"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_marginStart="6dp"
android:gravity="start" android:layout_marginTop="16dp"
android:orientation="vertical" android:text="@string/chats"
android:paddingStart="8dp" android:visibility="gone"
android:paddingEnd="8dp"> 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 <TextView
android:id="@+id/groupToolbarTitleView" android:id="@+id/groupToolbarTitleView"
android:layout_width="match_parent" style="@style/Widget.Vector.TextView.Title.Bold"
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_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="end" android:layout_gravity="center_vertical"
android:gravity="start" android:layout_marginStart="4dp"
android:maxLines="1" android:text="@string/all_chats"
android:textColor="?vctr_content_primary" android:textSize="22sp" />
android:visibility="gone"
tools:text="@tools:sample/lorem/random"
tools:visibility="visible" />
</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> </com.google.android.material.appbar.MaterialToolbar>
@ -149,12 +221,14 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/syncStateView" app:layout_constraintTop_toBottomOf="@id/syncStateView"
tools:visibility="visible" /> tools:visibility="gone" />
<androidx.fragment.app.FragmentContainerView <androidx.fragment.app.FragmentContainerView
android:id="@+id/roomListContainer" android:id="@+id/roomListContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginTop="0dp"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@id/bottomNavigationView" app:layout_constraintBottom_toTopOf="@id/bottomNavigationView"
app:layout_constraintTop_toBottomOf="@id/homeKeysBackupBanner" /> app:layout_constraintTop_toBottomOf="@id/homeKeysBackupBanner" />
@ -165,6 +239,46 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="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> </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:textColor="?vctr_content_primary"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@id/groupBottomSeparator" 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_constraintStart_toEndOf="@id/groupAvatarImageView"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/lorem/random" /> tools:text="@tools:sample/lorem/random" />
<ImageView <ImageView
android:id="@+id/groupAvatarChevron" android:id="@+id/chevron"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="21dp" 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" /> tools:text="@tools:sample/date/hhmm" />
<TextView <TextView
android:id="@+id/roomLastEventView" android:id="@+id/subtitleView"
style="@style/Widget.Vector.TextView.Body" style="@style/Widget.Vector.TextView.Body"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -213,7 +213,8 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/roomNameView" app:layout_constraintStart_toStartOf="@id/roomNameView"
app:layout_constraintTop_toBottomOf="@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 --> <!-- Margin bottom does not work, so I use space -->
<Space <Space
@ -221,7 +222,7 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="7dp" android:layout_height="7dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/roomLastEventView" app:layout_constraintTop_toBottomOf="@id/subtitleView"
tools:layout_marginStart="120dp" /> tools:layout_marginStart="120dp" />
<androidx.constraintlayout.widget.Barrier <androidx.constraintlayout.widget.Barrier

View File

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

View File

@ -101,12 +101,12 @@
android:padding="4dp" android:padding="4dp"
android:src="@drawable/ic_more_vertical" android:src="@drawable/ic_more_vertical"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/groupAvatarChevron" app:layout_constraintEnd_toStartOf="@id/chevron"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:tint="?vctr_content_secondary" /> app:tint="?vctr_content_secondary" />
<ImageView <ImageView
android:id="@+id/groupAvatarChevron" android:id="@+id/chevron"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="21dp" 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:id="@+id/menu_home_setting"
android:icon="@drawable/ic_settings_x" android:icon="@drawable/ic_settings_x"
android:title="@string/settings" android:title="@string/settings"
android:orderInCategory="100"
app:showAsAction="never" /> app:showAsAction="never" />
<item <item
android:id="@+id/menu_home_suggestion" android:id="@+id/menu_home_suggestion"
android:icon="@drawable/ic_material_bug_report" android:icon="@drawable/ic_material_bug_report"
android:orderInCategory="110"
android:title="@string/send_suggestion" /> android:title="@string/send_suggestion" />
<item <item
android:id="@+id/menu_home_report_bug" android:id="@+id/menu_home_report_bug"
android:icon="@drawable/ic_material_bug_report" android:icon="@drawable/ic_material_bug_report"
android:orderInCategory="120"
android:title="@string/send_bug_report" /> android:title="@string/send_bug_report" />
<item <item
android:id="@+id/menu_home_init_sync_legacy" android:id="@+id/menu_home_init_sync_legacy"
android:title="Do a legacy init sync" android:title="Do a legacy init sync"
android:orderInCategory="130"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
<item <item
android:id="@+id/menu_home_init_sync_optimized" android:id="@+id/menu_home_init_sync_optimized"
android:title="Do an optimized init sync" android:title="Do an optimized init sync"
android:orderInCategory="140"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
<item <item

View File

@ -1,9 +1,29 @@
<?xml version="1.0" encoding="utf-8"?> <?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 <item
android:id="@+id/menu_home_mark_all_as_read" android:id="@+id/menu_home_mark_all_as_read"
android:icon="@drawable/ic_material_done" android:icon="@drawable/ic_material_done"
android:orderInCategory="50"
android:title="@string/action_mark_all_as_read" /> android:title="@string/action_mark_all_as_read" />
<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> </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_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_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_invite">Invite</string>
<string name="room_participants_action_cancel_invite">Cancel invite</string> <string name="room_participants_action_cancel_invite">Cancel invite</string>
<string name="room_participants_action_ban">Ban</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_display_name">My display name</string>
<string name="settings_messages_containing_username">My username</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_encrypted_direct_messages">Encrypted direct messages</string>
<string name="settings_group_messages">Group messages</string> <string name="settings_group_messages">Group messages</string>
<string name="settings_encrypted_group_messages">Encrypted 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_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="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_room">"Rooms"</string>
<string name="fab_menu_create_chat">"Direct Messages"</string>
<!-- Create room screen --> <!-- Create room screen -->
<string name="create_room_action_create">"CREATE"</string> <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_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="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_idle">Waiting…</string>
<string name="send_file_step_encrypting_thumbnail">Encrypting thumbnail…</string> <string name="send_file_step_encrypting_thumbnail">Encrypting thumbnail…</string>
@ -3042,4 +3042,13 @@
<!-- Screen sharing --> <!-- Screen sharing -->
<string name="screen_sharing_notification_title">${app_name} Screen Sharing</string> <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="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> </resources>