1
0
mirror of https://github.com/vector-im/riotX-android synced 2025-10-06 16:22:41 +02:00

Compare commits

...

17 Commits

Author SHA1 Message Date
Valere
16a1cf2d42 RoomList build&diff on other thread 2020-09-18 13:48:20 +02:00
Valere
1f2de129a1 right-to-left symmetry 2020-09-18 12:45:41 +02:00
Valere
2ba5ea7674 cleaning 2020-09-18 11:06:39 +02:00
Valere
488083ca8c icon update 2020-09-18 10:56:33 +02:00
Valere
e53d39b82e revert grid items to small 2020-09-18 10:22:29 +02:00
Valere
063241b5a5 Fix fab padding when no bottomNavView 2020-09-18 10:22:13 +02:00
Valere
d6dc4b9a18 Labs settings for bottom/top nav and grid 2020-09-18 09:59:15 +02:00
Valere
7d0f5aa49e initial merge grid 2020-09-18 09:58:42 +02:00
Benoit Marty
071db7a4d8 With icon from design team 2020-09-10 17:06:32 +02:00
Benoit Marty
2c747cc847 Show switch if collapsed or for invites 2020-09-10 14:41:54 +02:00
Benoit Marty
87322ce8b0 Rename ids 2020-09-10 14:11:58 +02:00
ganfra
ad9f67b3bd Change tab background color 2020-09-09 23:42:15 +02:00
ganfra
3ae0586dac Change app id 2020-09-09 23:42:00 +02:00
ganfra
19b411776a Home: handle fabs 2020-09-09 20:22:40 +02:00
Benoit Marty
5286e6983c Experiment Grid view on Home 2020-09-09 19:44:45 +02:00
ganfra
ddbc2b0ade Room list : continue to experiment 2020-09-09 15:28:16 +02:00
ganfra
5f79530c4c Room list tab: start creating all classes and init ui 2020-09-08 20:02:41 +02:00
51 changed files with 1470 additions and 331 deletions

View File

@@ -112,7 +112,7 @@ android {
ndkVersion "21.3.6528147"
defaultConfig {
applicationId "im.vector.app"
applicationId "im.vector.app.tabs"
// Set to API 21: see #405
minSdkVersion 21
targetSdkVersion 29
@@ -186,7 +186,7 @@ android {
buildTypes {
debug {
applicationIdSuffix ".debug"
resValue "string", "app_name", "Element dbg"
resValue "string", "app_name", "Element Tabs"
resValue "bool", "debug_mode", "true"
buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"
@@ -195,7 +195,7 @@ android {
}
release {
resValue "string", "app_name", "Element (Riot.im)"
resValue "string", "app_name", "Element Grid"
resValue "bool", "debug_mode", "false"
buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"

View File

@@ -10,7 +10,7 @@
"client_info": {
"mobilesdk_app_id": "1:912726360885:android:4ef8f3a0021e774d",
"android_client_info": {
"package_name": "im.vector.app.debug"
"package_name": "im.vector.app.tabs.debug"
}
},
"oauth_client": [

View File

@@ -10,7 +10,7 @@
"client_info": {
"mobilesdk_app_id": "1:912726360885:android:4ef8f3a0021e774d",
"android_client_info": {
"package_name": "im.vector.app"
"package_name": "im.vector.app.tabs"
}
},
"oauth_client": [

View File

@@ -52,6 +52,7 @@ import im.vector.app.features.home.LoadingFragment
import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment
import im.vector.app.features.home.room.detail.RoomDetailFragment
import im.vector.app.features.home.room.list.RoomListFragment
import im.vector.app.features.home.room.list.tabs.RoomListTabsFragment
import im.vector.app.features.login.LoginCaptchaFragment
import im.vector.app.features.login.LoginFragment
import im.vector.app.features.login.LoginGenericTextInputFormFragment
@@ -124,6 +125,11 @@ interface FragmentModule {
@FragmentKey(RoomListFragment::class)
fun bindRoomListFragment(fragment: RoomListFragment): Fragment
@Binds
@IntoMap
@FragmentKey(RoomListTabsFragment::class)
fun bindRoomListTabsFragment(fragment: RoomListTabsFragment): Fragment
@Binds
@IntoMap
@FragmentKey(LocalePickerFragment::class)

View File

@@ -123,7 +123,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
drawerLayout.closeDrawer(GravityCompat.START)
replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java)
}
}.exhaustive
}
}
.disposeOnDestroy()

View File

@@ -25,4 +25,5 @@ sealed class HomeActivitySharedAction : VectorSharedAction {
object OpenDrawer : HomeActivitySharedAction()
object CloseDrawer : HomeActivitySharedAction()
object OpenGroup : HomeActivitySharedAction()
data class OnDisplayModeSelected(val displayMode: RoomListDisplayMode): HomeActivitySharedAction()
}

View File

@@ -19,5 +19,5 @@ package im.vector.app.features.home
import im.vector.app.core.platform.VectorViewModelAction
sealed class HomeDetailAction : VectorViewModelAction {
data class SwitchDisplayMode(val displayMode: RoomListDisplayMode) : HomeDetailAction()
data class SwitchDisplayMode(val displayMode: HomeDisplayMode) : HomeDetailAction()
}

View File

@@ -18,13 +18,19 @@ package im.vector.app.features.home
import android.os.Bundle
import android.view.View
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat
import androidx.core.view.forEach
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.Observer
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import com.google.android.material.badge.BadgeDrawable
import com.google.android.material.behavior.HideBottomViewOnScrollBehavior
import im.vector.app.R
import im.vector.app.core.extensions.addFragment
import im.vector.app.core.extensions.commitTransaction
import im.vector.app.core.glide.GlideApp
import im.vector.app.core.platform.ToolbarConfigurable
@@ -33,11 +39,14 @@ import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.ui.views.ActiveCallView
import im.vector.app.core.ui.views.ActiveCallViewHolder
import im.vector.app.core.ui.views.KeysBackupBanner
import im.vector.app.core.utils.DimensionConverter
import im.vector.app.features.call.SharedActiveCallViewModel
import im.vector.app.features.call.VectorCallActivity
import im.vector.app.features.call.WebRtcPeerConnectionManager
import im.vector.app.features.home.room.list.RoomListFragment
import im.vector.app.features.home.room.list.RoomListParams
import im.vector.app.features.home.room.list.tabs.RoomListTabsFragment
import im.vector.app.features.home.room.list.widget.FabMenuView
import im.vector.app.features.popup.PopupAlertManager
import im.vector.app.features.popup.VerificationVectorAlert
import im.vector.app.features.settings.VectorPreferences
@@ -63,8 +72,9 @@ class HomeDetailFragment @Inject constructor(
private val avatarRenderer: AvatarRenderer,
private val alertManager: PopupAlertManager,
private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager,
private val vectorPreferences: VectorPreferences
) : VectorBaseFragment(), KeysBackupBanner.Delegate, ActiveCallView.Callback, ServerBackupStatusViewModel.Factory {
private val vectorPreferences: VectorPreferences,
private val dimensionConverter: DimensionConverter
) : VectorBaseFragment(), KeysBackupBanner.Delegate, ActiveCallView.Callback, ServerBackupStatusViewModel.Factory, FabMenuView.Listener {
private val viewModel: HomeDetailViewModel by fragmentViewModel()
private val unknownDeviceDetectorSharedViewModel: UnknownDeviceDetectorSharedViewModel by activityViewModel()
@@ -81,7 +91,7 @@ class HomeDetailFragment @Inject constructor(
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java)
sharedCallActionViewModel = activityViewModelProvider.get(SharedActiveCallViewModel::class.java)
setupCreateRoomButton()
setupBottomNavigationView()
setupToolbar()
setupKeysBackupBanner()
@@ -95,7 +105,9 @@ class HomeDetailFragment @Inject constructor(
viewModel.selectSubscribe(this, HomeDetailViewState::groupSummary) { groupSummary ->
onGroupChange(groupSummary.orNull())
}
viewModel.selectSubscribe(this, HomeDetailViewState::displayMode) { displayMode ->
if (vectorPreferences.labUseTabNavigation()) return@selectSubscribe
switchDisplayMode(displayMode)
}
@@ -117,12 +129,74 @@ class HomeDetailFragment @Inject constructor(
}
}
sharedActionViewModel.observe()
.subscribe {
when (it) {
is HomeActivitySharedAction.OnDisplayModeSelected -> renderDisplayMode(it.displayMode)
}
}
.disposeOnDestroyView()
sharedCallActionViewModel
.activeCall
.observe(viewLifecycleOwner, Observer {
activeCallViewHolder.updateCall(it, webRtcPeerConnectionManager)
invalidateOptionsMenu()
})
if (vectorPreferences.labUseTabNavigation()) {
addFragment(R.id.roomListContainer, RoomListTabsFragment::class.java)
bottomNavigationView.isVisible = false
}
}
override fun onDestroyView() {
super.onDestroyView()
createChatFabMenu.listener = null
}
private fun renderDisplayMode(displayMode: RoomListDisplayMode) {
when (displayMode) {
RoomListDisplayMode.ALL,
RoomListDisplayMode.NOTIFICATIONS,
RoomListDisplayMode.FAVORITES,
RoomListDisplayMode.LOW_PRIORITY,
RoomListDisplayMode.INVITES -> {
createChatFabMenu.getHideBottomViewOnScrollBehavior().slideUp(createChatFabMenu)
createChatFabMenu.show()
createChatRoomButton.isVisible = false
createGroupRoomButton.isVisible = false
}
RoomListDisplayMode.PEOPLE -> {
createChatFabMenu.isVisible = false
createChatRoomButton.isVisible = true
createChatRoomButton.getHideBottomViewOnScrollBehavior().slideUp(createChatRoomButton)
createChatRoomButton.show()
createGroupRoomButton.isVisible = false
}
RoomListDisplayMode.ROOMS -> {
createChatFabMenu.isVisible = false
createChatRoomButton.isVisible = false
createGroupRoomButton.isVisible = true
createGroupRoomButton.getHideBottomViewOnScrollBehavior().slideUp(createGroupRoomButton)
createGroupRoomButton.show()
}
else -> {
createChatFabMenu.isVisible = false
createChatRoomButton.isVisible = false
createGroupRoomButton.isVisible = false
}
}
}
private fun setupCreateRoomButton() {
createChatFabMenu.listener = this
createChatRoomButton.debouncedClicks {
createDirectChat()
}
createGroupRoomButton.debouncedClicks {
openRoomDirectory()
}
}
override fun onResume() {
@@ -131,17 +205,46 @@ class HomeDetailFragment @Inject constructor(
checkNotificationTabStatus()
}
private fun checkNotificationTabStatus() {
val wasVisible = bottomNavigationView.menu.findItem(R.id.bottom_action_notification).isVisible
bottomNavigationView.menu.findItem(R.id.bottom_action_notification).isVisible = vectorPreferences.labAddNotificationTab()
if (wasVisible && !vectorPreferences.labAddNotificationTab()) {
// As we hide it check if it's not the current item!
withState(viewModel) {
if (it.displayMode.toMenuId() == R.id.bottom_action_notification) {
viewModel.handle(HomeDetailAction.SwitchDisplayMode(RoomListDisplayMode.PEOPLE))
}
private fun checkNotificationTabStatus() = withState(viewModel) { state ->
bottomNavigationView.menu.forEach { menuItem ->
menuItem.isVisible = state.tabList.indexOfFirst { it.toMenuId() == menuItem.itemId } != -1
}
if (vectorPreferences.labUseTabNavigation()) {
addFragment(R.id.roomListContainer, RoomListTabsFragment::class.java)
bottomNavigationView.isVisible = false
createGroupRoomButton.updateLayoutParams<CoordinatorLayout.LayoutParams> {
this.bottomMargin = dimensionConverter.dpToPx(16)
}
createChatRoomButton.updateLayoutParams<CoordinatorLayout.LayoutParams> {
this.bottomMargin = dimensionConverter.dpToPx(16)
}
createChatFabMenu.updateLayoutParams<CoordinatorLayout.LayoutParams> {
this.bottomMargin = dimensionConverter.dpToPx(0)
}
} else {
bottomNavigationView.isVisible = true
switchDisplayMode(state.displayMode)
createGroupRoomButton.updateLayoutParams<CoordinatorLayout.LayoutParams> {
this.bottomMargin = dimensionConverter.dpToPx(64)
}
createChatRoomButton.updateLayoutParams<CoordinatorLayout.LayoutParams> {
this.bottomMargin = dimensionConverter.dpToPx(64)
}
createChatFabMenu.updateLayoutParams<CoordinatorLayout.LayoutParams> {
this.bottomMargin = dimensionConverter.dpToPx(48)
}
}
// val wasVisible = bottomNavigationView.menu.findItem(R.id.bottom_action_notification).isVisible
// bottomNavigationView.menu.findItem(R.id.bottom_action_notification).isVisible = vectorPreferences.labAddNotificationTab()
// if (wasVisible && !vectorPreferences.labAddNotificationTab()) {
// // As we hide it check if it's not the current item!
// withState(viewModel) {
// if (it.displayMode.toMenuId() == R.id.bottom_action_notification) {
// viewModel.handle(HomeDetailAction.SwitchDisplayMode(HomeDisplayMode.CHATS))
// }
// }
// }
}
private fun promptForNewUnknownDevices(uid: String, state: UnknownDevicesState, newest: DeviceInfo) {
@@ -205,6 +308,7 @@ class HomeDetailFragment @Inject constructor(
groupSummary?.let {
// Use GlideApp with activity context to avoid the glideRequests to be paused
avatarRenderer.render(it.toMatrixItem(), groupToolbarAvatarImageView, GlideApp.with(requireActivity()))
groupToolbarTitleView.text = it.displayName
}
}
@@ -245,9 +349,12 @@ class HomeDetailFragment @Inject constructor(
bottomNavigationView.menu.findItem(R.id.bottom_action_notification).isVisible = vectorPreferences.labAddNotificationTab()
bottomNavigationView.setOnNavigationItemSelectedListener {
val displayMode = when (it.itemId) {
R.id.bottom_action_people -> RoomListDisplayMode.PEOPLE
R.id.bottom_action_rooms -> RoomListDisplayMode.ROOMS
else -> RoomListDisplayMode.NOTIFICATIONS
R.id.bottom_action_chats -> HomeDisplayMode.CHATS
// R.id.bottom_action_you -> HomeDisplayMode.YOU
R.id.bottom_action_favourites -> HomeDisplayMode.FAVORITES
R.id.bottom_action_people -> HomeDisplayMode.PEOPLE
R.id.bottom_action_rooms -> HomeDisplayMode.ROOMS
else -> HomeDisplayMode.NOTIFICATIONS
}
viewModel.handle(HomeDetailAction.SwitchDisplayMode(displayMode))
true
@@ -265,12 +372,12 @@ class HomeDetailFragment @Inject constructor(
// }
}
private fun switchDisplayMode(displayMode: RoomListDisplayMode) {
private fun switchDisplayMode(displayMode: HomeDisplayMode) {
groupToolbarTitleView.setText(displayMode.titleRes)
updateSelectedFragment(displayMode)
}
private fun updateSelectedFragment(displayMode: RoomListDisplayMode) {
private fun updateSelectedFragment(displayMode: HomeDisplayMode) {
val fragmentTag = "FRAGMENT_TAG_${displayMode.name}"
val fragmentToShow = childFragmentManager.findFragmentByTag(fragmentTag)
childFragmentManager.commitTransaction {
@@ -280,8 +387,16 @@ class HomeDetailFragment @Inject constructor(
detach(it)
}
if (fragmentToShow == null) {
val params = RoomListParams(displayMode)
// if (displayMode == HomeDisplayMode.CHATS) {
// add(R.id.roomListContainer, RoomListTabsFragment::class.java, Bundle.EMPTY, fragmentTag)
// }
// else if (displayMode == HomeDisplayMode.YOU) {
// add(R.id.roomListContainer, VectorSettingsGeneralFragment::class.java, Bundle.EMPTY, fragmentTag)
// }
// else {
val params = RoomListParams(displayMode.toRoomMode())
add(R.id.roomListContainer, RoomListFragment::class.java, params.toMvRxBundle(), fragmentTag)
// }
} else {
attach(fragmentToShow)
}
@@ -302,9 +417,9 @@ class HomeDetailFragment @Inject constructor(
override fun invalidate() = withState(viewModel) {
Timber.v(it.toString())
bottomNavigationView.getOrCreateBadge(R.id.bottom_action_people).render(it.notificationCountPeople, it.notificationHighlightPeople)
bottomNavigationView.getOrCreateBadge(R.id.bottom_action_rooms).render(it.notificationCountRooms, it.notificationHighlightRooms)
bottomNavigationView.getOrCreateBadge(R.id.bottom_action_notification).render(it.notificationCountCatchup, it.notificationHighlightCatchup)
// bottomNavigationView.getOrCreateBadge(R.id.bottom_action_people).render(it.notificationCountPeople, it.notificationHighlightPeople)
// bottomNavigationView.getOrCreateBadge(R.id.bottom_action_rooms).render(it.notificationCountRooms, it.notificationHighlightRooms)
// bottomNavigationView.getOrCreateBadge(R.id.bottom_action_notification).render(it.notificationCountCatchup, it.notificationHighlightCatchup)
syncStateView.render(it.syncState)
}
@@ -320,10 +435,28 @@ class HomeDetailFragment @Inject constructor(
}
}
private fun RoomListDisplayMode.toMenuId() = when (this) {
RoomListDisplayMode.PEOPLE -> R.id.bottom_action_people
RoomListDisplayMode.ROOMS -> R.id.bottom_action_rooms
else -> R.id.bottom_action_notification
// private fun RoomListDisplayMode.toMenuId() = when (this) {
// // RoomListDisplayMode.PEOPLE -> R.id.bottom_action_people
// // RoomListDisplayMode.ROOMS -> R.id.bottom_action_rooms
// else -> R.id.bottom_action_notification
// }
private fun HomeDisplayMode.toMenuId() = when (this) {
HomeDisplayMode.CHATS -> R.id.bottom_action_chats
HomeDisplayMode.FAVORITES -> R.id.bottom_action_favourites
HomeDisplayMode.ROOMS -> R.id.bottom_action_rooms
HomeDisplayMode.PEOPLE -> R.id.bottom_action_people
HomeDisplayMode.NOTIFICATIONS -> R.id.bottom_action_notification
// HomeDisplayMode.YOU -> R.id.bottom_action_you
}
private fun HomeDisplayMode.toRoomMode() = when (this) {
HomeDisplayMode.CHATS -> RoomListDisplayMode.ALL
HomeDisplayMode.FAVORITES -> RoomListDisplayMode.FAVORITES
HomeDisplayMode.NOTIFICATIONS -> RoomListDisplayMode.NOTIFICATIONS
HomeDisplayMode.ROOMS -> RoomListDisplayMode.ROOMS
HomeDisplayMode.PEOPLE -> RoomListDisplayMode.PEOPLE
// else -> RoomListDisplayMode.ROOMS
}
override fun onTapToReturnToCall() {
@@ -342,7 +475,19 @@ class HomeDetailFragment @Inject constructor(
}
}
override fun openRoomDirectory(initialFilter: String) {
navigator.openRoomDirectory(requireActivity(), initialFilter)
}
override fun createDirectChat() {
navigator.openCreateDirectRoom(requireActivity())
}
override fun create(initialState: ServerBackupStatusViewState): ServerBackupStatusViewModel {
return serverBackupStatusViewModelFactory.create(initialState)
}
private fun View.getHideBottomViewOnScrollBehavior(): HideBottomViewOnScrollBehavior<View> {
return (layoutParams as CoordinatorLayout.LayoutParams).behavior as HideBottomViewOnScrollBehavior
}
}

View File

@@ -21,7 +21,6 @@ import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.app.core.di.HasScreenInjector
import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider
@@ -52,9 +51,10 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
companion object : MvRxViewModelFactory<HomeDetailViewModel, HomeDetailViewState> {
override fun initialState(viewModelContext: ViewModelContext): HomeDetailViewState? {
val uiStateRepository = (viewModelContext.activity as HasScreenInjector).injector().uiStateRepository()
// val uiStateRepository = (viewModelContext.activity as HasScreenInjector).injector().uiStateRepository()
// TODO
return HomeDetailViewState(
displayMode = uiStateRepository.getDisplayMode()
displayMode = HomeDisplayMode.CHATS // uiStateRepository.getDisplayMode()
)
}
@@ -83,7 +83,7 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
copy(displayMode = action.displayMode)
}
uiStateRepository.storeDisplayMode(action.displayMode)
// uiStateRepository.storeDisplayMode(action.displayMode)
}
}

View File

@@ -27,7 +27,8 @@ import org.matrix.android.sdk.api.session.sync.SyncState
data class HomeDetailViewState(
val groupSummary: Option<GroupSummary> = Option.empty(),
val asyncRooms: Async<List<RoomSummary>> = Uninitialized,
val displayMode: RoomListDisplayMode = RoomListDisplayMode.PEOPLE,
val displayMode: HomeDisplayMode = HomeDisplayMode.CHATS,
val tabList: List<HomeDisplayMode> = listOf(HomeDisplayMode.CHATS, HomeDisplayMode.FAVORITES, HomeDisplayMode.ROOMS, HomeDisplayMode.PEOPLE),
val notificationCountCatchup: Int = 0,
val notificationHighlightCatchup: Boolean = false,
val notificationCountPeople: Int = 0,

View File

@@ -17,9 +17,13 @@
package im.vector.app.features.home
import android.os.Handler
import android.os.HandlerThread
import dagger.Module
import dagger.Provides
import im.vector.app.features.home.room.detail.timeline.EpoxyControllerDiffHandler
import im.vector.app.features.home.room.detail.timeline.EpoxyControllerModelHandler
import im.vector.app.features.home.room.detail.timeline.TimelineEventControllerHandler
import im.vector.app.features.home.room.detail.timeline.helper.EpoxyHandlerHelper
import im.vector.app.features.home.room.detail.timeline.helper.TimelineAsyncHelper
@Module
@@ -31,4 +35,18 @@ object HomeModule {
fun providesTimelineBackgroundHandler(): Handler {
return TimelineAsyncHelper.getBackgroundHandler()
}
@Provides
@JvmStatic
@EpoxyControllerModelHandler
fun providesEpoxyControllerModelHandler(): Handler {
return EpoxyHandlerHelper.modelBuildingHandler
}
@Provides
@JvmStatic
@EpoxyControllerDiffHandler
fun providesEpoxyControllerDiffHandler(): Handler {
return EpoxyHandlerHelper.diffingHandler
}
}

View File

@@ -19,8 +19,21 @@ package im.vector.app.features.home
import androidx.annotation.StringRes
import im.vector.app.R
enum class RoomListDisplayMode(@StringRes val titleRes: Int) {
enum class HomeDisplayMode(@StringRes val titleRes: Int) {
CHATS(R.string.home_bottom_tab_chats),
NOTIFICATIONS(R.string.bottom_action_notification),
FAVORITES(R.string.room_recents_favourites),
PEOPLE(R.string.bottom_action_people_x),
ROOMS(R.string.bottom_action_rooms),
// YOU(R.string.home_bottom_tab_you),
}
enum class RoomListDisplayMode(@StringRes val titleRes: Int) {
ALL(R.string.room_list_tabs_all),
FAVORITES(R.string.room_recents_favourites),
NOTIFICATIONS(R.string.bottom_action_notification),
LOW_PRIORITY(R.string.room_recents_low_priority),
INVITES(R.string.invitations_header),
PEOPLE(R.string.bottom_action_people_x),
ROOMS(R.string.bottom_action_rooms),
FILTERED(/* Not used */ 0)

View File

@@ -63,8 +63,10 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
private val mergedHeaderItemFactory: MergedHeaderItemFactory,
@TimelineEventControllerHandler
private val backgroundHandler: Handler
) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener, EpoxyController.Interceptor {
private val backgroundHandler: Handler,
@EpoxyControllerModelHandler
private val modelBuilderHandler: Handler
) : EpoxyController(modelBuilderHandler, backgroundHandler), Timeline.Listener, EpoxyController.Interceptor {
interface Callback : BaseCallback, ReactionPillCallback, AvatarCallback, UrlClickCallback, ReadReceiptsCallback {
fun onLoadMore(direction: Timeline.Direction)

View File

@@ -21,3 +21,11 @@ import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class TimelineEventControllerHandler
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class EpoxyControllerModelHandler
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class EpoxyControllerDiffHandler

View File

@@ -0,0 +1,37 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.home.room.detail.timeline.helper
import android.os.Handler
import android.os.HandlerThread
object EpoxyHandlerHelper {
val modelBuildingHandler: Handler
val diffingHandler: Handler
init {
val handlerThread = HandlerThread("EpoxyControllerModelHandler")
handlerThread.start()
modelBuildingHandler = Handler(handlerThread.looper)
val diffHandlerThread = HandlerThread("EpoxyControllerDiffHandler")
diffHandlerThread.start()
diffingHandler = Handler(diffHandlerThread.looper)
}
}

View File

@@ -17,9 +17,11 @@
package im.vector.app.features.home.room.list
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
@@ -32,8 +34,11 @@ abstract class RoomCategoryItem : VectorEpoxyModel<RoomCategoryItem.Holder>() {
@EpoxyAttribute lateinit var title: CharSequence
@EpoxyAttribute var expanded: Boolean = false
@EpoxyAttribute var mode: RoomListViewState.CategoryMode = RoomListViewState.CategoryMode.List
@EpoxyAttribute var unreadNotificationCount: Int = 0
@EpoxyAttribute var showHighlighted: Boolean = false
@EpoxyAttribute var showSwitchMode: Boolean = true
@EpoxyAttribute var changeModeListener: ((RoomListViewState.CategoryMode) -> Unit)? = null
@EpoxyAttribute var listener: (() -> Unit)? = null
override fun bind(holder: Holder) {
@@ -47,11 +52,32 @@ abstract class RoomCategoryItem : VectorEpoxyModel<RoomCategoryItem.Holder>() {
holder.titleView.setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null)
holder.titleView.text = title
holder.rootView.setOnClickListener { listener?.invoke() }
if (showSwitchMode && expanded) {
holder.modeSwitch.isVisible = true
holder.modeSwitch.setImageResource(
when (mode) {
RoomListViewState.CategoryMode.Grid -> R.drawable.ic_mode_grid_24dp
RoomListViewState.CategoryMode.List -> R.drawable.ic_mode_list_24dp
}
)
holder.modeSwitch.setOnClickListener {
changeModeListener?.invoke(
when (mode) {
RoomListViewState.CategoryMode.Grid -> RoomListViewState.CategoryMode.List
RoomListViewState.CategoryMode.List -> RoomListViewState.CategoryMode.Grid
}
)
}
} else {
holder.modeSwitch.isVisible = false
}
}
class Holder : VectorEpoxyHolder() {
val unreadCounterBadgeView by bind<UnreadCounterBadgeView>(R.id.roomCategoryUnreadCounterBadgeView)
val titleView by bind<TextView>(R.id.roomCategoryTitleView)
val modeSwitch by bind<ImageView>(R.id.roomCategoryMode)
val rootView by bind<ViewGroup>(R.id.roomCategoryRootView)
}
}

View File

@@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.room.notification.RoomNotificationStat
sealed class RoomListAction : VectorViewModelAction {
data class SelectRoom(val roomSummary: RoomSummary) : RoomListAction()
data class ToggleCategory(val category: RoomCategory) : RoomListAction()
data class ChangeCategoryMode(val category: RoomCategory, val newCategoryMode: RoomListViewState.CategoryMode) : RoomListAction()
data class AcceptInvitation(val roomSummary: RoomSummary) : RoomListAction()
data class RejectInvitation(val roomSummary: RoomSummary) : RoomListAction()
data class FilterWith(val filter: String) : RoomListAction()

View File

@@ -17,9 +17,10 @@
package im.vector.app.features.home.room.list
import im.vector.app.features.home.RoomListDisplayMode
import io.reactivex.functions.Predicate
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import io.reactivex.functions.Predicate
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
class RoomListDisplayModeFilter(private val displayMode: RoomListDisplayMode) : Predicate<RoomSummary> {
@@ -30,9 +31,13 @@ class RoomListDisplayModeFilter(private val displayMode: RoomListDisplayMode) :
return when (displayMode) {
RoomListDisplayMode.NOTIFICATIONS ->
roomSummary.notificationCount > 0 || roomSummary.membership == Membership.INVITE || roomSummary.userDrafts.isNotEmpty()
RoomListDisplayMode.PEOPLE -> roomSummary.isDirect && roomSummary.membership.isActive()
RoomListDisplayMode.ROOMS -> !roomSummary.isDirect && roomSummary.membership.isActive()
RoomListDisplayMode.FILTERED -> roomSummary.membership == Membership.JOIN
RoomListDisplayMode.PEOPLE -> roomSummary.isDirect && roomSummary.membership == Membership.JOIN
RoomListDisplayMode.ROOMS -> !roomSummary.isDirect && roomSummary.membership == Membership.JOIN
RoomListDisplayMode.FILTERED -> roomSummary.membership == Membership.JOIN
RoomListDisplayMode.ALL -> roomSummary.membership.isActive()
RoomListDisplayMode.FAVORITES -> roomSummary.membership == Membership.JOIN && roomSummary.tags.any { it.name == RoomTag.ROOM_TAG_FAVOURITE }
RoomListDisplayMode.LOW_PRIORITY -> roomSummary.membership == Membership.JOIN && roomSummary.tags.any { it.name == RoomTag.ROOM_TAG_LOW_PRIORITY }
RoomListDisplayMode.INVITES -> roomSummary.membership == Membership.INVITE
}
}
}

View File

@@ -18,13 +18,14 @@ package im.vector.app.features.home.room.list
import android.os.Bundle
import android.os.Parcelable
import android.util.DisplayMetrics
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.airbnb.epoxy.OnModelBuildFinishedListener
import com.airbnb.mvrx.Fail
@@ -37,22 +38,21 @@ import im.vector.app.R
import im.vector.app.core.epoxy.LayoutManagerStateRestorer
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.OnBackPressed
import im.vector.app.core.platform.StateView
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.DimensionConverter
import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.home.room.list.actions.RoomListActionsArgs
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
import im.vector.app.features.home.room.list.widget.FabMenuView
import im.vector.app.features.notifications.NotificationDrawerManager
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_room_list.*
import org.matrix.android.sdk.api.failure.Failure
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.notification.RoomNotificationState
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_room_list.*
import javax.inject.Inject
@Parcelize
@@ -64,9 +64,9 @@ class RoomListFragment @Inject constructor(
private val roomController: RoomSummaryController,
val roomListViewModelFactory: RoomListViewModel.Factory,
private val notificationDrawerManager: NotificationDrawerManager,
private val sharedViewPool: RecyclerView.RecycledViewPool
) : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, FabMenuView.Listener {
private val sharedViewPool: RecyclerView.RecycledViewPool,
private val dimensionConverter: DimensionConverter
) : VectorBaseFragment(), RoomSummaryController.Listener {
private var modelBuildListener: OnModelBuildFinishedListener? = null
private lateinit var sharedActionViewModel: RoomListQuickActionsSharedActionViewModel
@@ -98,20 +98,16 @@ class RoomListFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupCreateRoomButton()
setupRecyclerView()
sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
roomListViewModel.observeViewEvents {
when (it) {
is RoomListViewEvents.Loading -> showLoading(it.message)
is RoomListViewEvents.Failure -> showFailure(it.throwable)
is RoomListViewEvents.Loading -> showLoading(it.message)
is RoomListViewEvents.Failure -> showFailure(it.throwable)
is RoomListViewEvents.SelectRoom -> handleSelectRoom(it)
is RoomListViewEvents.Done -> Unit
is RoomListViewEvents.Done -> Unit
}.exhaustive
}
createChatFabMenu.listener = this
sharedActionViewModel
.observe()
.subscribe { handleQuickActions(it) }
@@ -128,7 +124,6 @@ class RoomListFragment @Inject constructor(
roomListView.cleanup()
roomController.listener = null
stateRestorer.clear()
createChatFabMenu.listener = null
super.onDestroyView()
}
@@ -136,44 +131,44 @@ class RoomListFragment @Inject constructor(
navigator.openRoom(requireActivity(), event.roomSummary.roomId)
}
private fun setupCreateRoomButton() {
when (roomListParams.displayMode) {
RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.isVisible = true
RoomListDisplayMode.PEOPLE -> createChatRoomButton.isVisible = true
RoomListDisplayMode.ROOMS -> createGroupRoomButton.isVisible = true
else -> Unit // No button in this mode
}
createChatRoomButton.debouncedClicks {
createDirectChat()
}
createGroupRoomButton.debouncedClicks {
openRoomDirectory()
}
// Hide FAB when list is scrolling
roomListView.addOnScrollListener(
object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
createChatFabMenu.removeCallbacks(showFabRunnable)
when (newState) {
RecyclerView.SCROLL_STATE_IDLE -> {
createChatFabMenu.postDelayed(showFabRunnable, 250)
}
RecyclerView.SCROLL_STATE_DRAGGING,
RecyclerView.SCROLL_STATE_SETTLING -> {
when (roomListParams.displayMode) {
RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.hide()
RoomListDisplayMode.PEOPLE -> createChatRoomButton.hide()
RoomListDisplayMode.ROOMS -> createGroupRoomButton.hide()
else -> Unit
}
}
}
}
})
}
// private fun setupCreateRoomButton() {
// when (roomListParams.displayMode) {
// RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.isVisible = true
// RoomListDisplayMode.PEOPLE -> createChatRoomButton.isVisible = true
// RoomListDisplayMode.ROOMS -> createGroupRoomButton.isVisible = true
// else -> Unit // No button in this mode
// }
//
// createChatRoomButton.debouncedClicks {
// createDirectChat()
// }
// createGroupRoomButton.debouncedClicks {
// openRoomDirectory()
// }
//
// // Hide FAB when list is scrolling
// roomListView.addOnScrollListener(
// object : RecyclerView.OnScrollListener() {
// override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
// createChatFabMenu.removeCallbacks(showFabRunnable)
//
// when (newState) {
// RecyclerView.SCROLL_STATE_IDLE -> {
// createChatFabMenu.postDelayed(showFabRunnable, 250)
// }
// RecyclerView.SCROLL_STATE_DRAGGING,
// RecyclerView.SCROLL_STATE_SETTLING -> {
// when (roomListParams.displayMode) {
// RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.hide()
// RoomListDisplayMode.PEOPLE -> createChatRoomButton.hide()
// RoomListDisplayMode.ROOMS -> createGroupRoomButton.hide()
// else -> Unit
// }
// }
// }
// }
// })
// }
fun filterRoomsWith(filter: String) {
// Scroll the list to top
@@ -182,16 +177,9 @@ class RoomListFragment @Inject constructor(
roomListViewModel.handle(RoomListAction.FilterWith(filter))
}
override fun openRoomDirectory(initialFilter: String) {
navigator.openRoomDirectory(requireActivity(), initialFilter)
}
override fun createDirectChat() {
navigator.openCreateDirectRoom(requireActivity())
}
private fun setupRecyclerView() {
val layoutManager = LinearLayoutManager(context)
val numOfColumns = getNumberOfColumns()
val layoutManager = GridLayoutManager(context, numOfColumns)
stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
roomListView.layoutManager = layoutManager
roomListView.itemAnimator = RoomListAnimator()
@@ -202,40 +190,49 @@ class RoomListFragment @Inject constructor(
roomController.addModelBuildListener(modelBuildListener)
roomListView.adapter = roomController.adapter
stateView.contentView = roomListView
roomController.spanCount = numOfColumns
layoutManager.spanSizeLookup = roomController.spanSizeLookup
}
private val showFabRunnable = Runnable {
if (isAdded) {
when (roomListParams.displayMode) {
RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.show()
RoomListDisplayMode.PEOPLE -> createChatRoomButton.show()
RoomListDisplayMode.ROOMS -> createGroupRoomButton.show()
else -> Unit
}
}
private fun getNumberOfColumns(): Int {
val displayMetrics = DisplayMetrics()
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
return dimensionConverter.pxToDp(displayMetrics.widthPixels) / ITEM_SIZE_DP
}
// private val showFabRunnable = Runnable {
// if (isAdded) {
// when (roomListParams.displayMode) {
// RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.show()
// RoomListDisplayMode.PEOPLE -> createChatRoomButton.show()
// RoomListDisplayMode.ROOMS -> createGroupRoomButton.show()
// else -> Unit
// }
// }
// }
private fun handleQuickActions(quickAction: RoomListQuickActionsSharedAction) {
when (quickAction) {
is RoomListQuickActionsSharedAction.NotificationsAllNoisy -> {
is RoomListQuickActionsSharedAction.NotificationsAllNoisy -> {
roomListViewModel.handle(RoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.ALL_MESSAGES_NOISY))
}
is RoomListQuickActionsSharedAction.NotificationsAll -> {
is RoomListQuickActionsSharedAction.NotificationsAll -> {
roomListViewModel.handle(RoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.ALL_MESSAGES))
}
is RoomListQuickActionsSharedAction.NotificationsMentionsOnly -> {
roomListViewModel.handle(RoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.MENTIONS_ONLY))
}
is RoomListQuickActionsSharedAction.NotificationsMute -> {
is RoomListQuickActionsSharedAction.NotificationsMute -> {
roomListViewModel.handle(RoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.MUTE))
}
is RoomListQuickActionsSharedAction.Settings -> {
is RoomListQuickActionsSharedAction.Settings -> {
navigator.openRoomProfile(requireActivity(), quickAction.roomId)
}
is RoomListQuickActionsSharedAction.Favorite -> {
is RoomListQuickActionsSharedAction.Favorite -> {
roomListViewModel.handle(RoomListAction.ToggleFavorite(quickAction.roomId))
}
is RoomListQuickActionsSharedAction.Leave -> {
is RoomListQuickActionsSharedAction.Leave -> {
AlertDialog.Builder(requireContext())
.setTitle(R.string.room_participants_leave_prompt_title)
.setMessage(R.string.room_participants_leave_prompt_msg)
@@ -252,12 +249,16 @@ class RoomListFragment @Inject constructor(
override fun invalidate() = withState(roomListViewModel) { state ->
when (state.asyncFilteredRooms) {
is Incomplete -> renderLoading()
is Success -> renderSuccess(state)
is Fail -> renderFailure(state.asyncFilteredRooms.error)
is Success -> renderSuccess(state)
is Fail -> renderFailure(state.asyncFilteredRooms.error)
}
roomController.update(state)
// Mark all as read menu
when (roomListParams.displayMode) {
RoomListDisplayMode.ALL,
RoomListDisplayMode.FAVORITES,
RoomListDisplayMode.LOW_PRIORITY,
RoomListDisplayMode.INVITES,
RoomListDisplayMode.NOTIFICATIONS,
RoomListDisplayMode.PEOPLE,
RoomListDisplayMode.ROOMS -> {
@@ -302,19 +303,19 @@ class RoomListFragment @Inject constructor(
getString(R.string.room_list_catchup_empty_body))
}
}
RoomListDisplayMode.PEOPLE ->
RoomListDisplayMode.PEOPLE ->
StateView.State.Empty(
getString(R.string.room_list_people_empty_title),
ContextCompat.getDrawable(requireContext(), R.drawable.ic_home_bottom_chat),
getString(R.string.room_list_people_empty_body)
)
RoomListDisplayMode.ROOMS ->
RoomListDisplayMode.ROOMS ->
StateView.State.Empty(
getString(R.string.room_list_rooms_empty_title),
ContextCompat.getDrawable(requireContext(), R.drawable.ic_home_bottom_group),
getString(R.string.room_list_rooms_empty_body)
)
else ->
else ->
// Always display the content in this mode, because if the footer
StateView.State.Content
}
@@ -333,13 +334,6 @@ class RoomListFragment @Inject constructor(
stateView.state = StateView.State.Error(message)
}
override fun onBackPressed(toolbarButton: Boolean): Boolean {
if (createChatFabMenu.onBackPressed()) {
return true
}
return false
}
// RoomSummaryController.Callback **************************************************************
override fun onRoomClicked(room: RoomSummary) {
@@ -368,7 +362,23 @@ class RoomListFragment @Inject constructor(
roomListViewModel.handle(RoomListAction.ToggleCategory(roomCategory))
}
override fun onChangeModeRoomCategory(roomCategory: RoomCategory, newMode: RoomListViewState.CategoryMode) {
roomListViewModel.handle(RoomListAction.ChangeCategoryMode(roomCategory, newMode))
}
override fun createRoom(initialName: String) {
navigator.openCreateRoom(requireActivity(), initialName)
}
override fun createDirectChat() {
TODO("Not yet implemented")
}
override fun openRoomDirectory(initialFilter: String) {
TODO("Not yet implemented")
}
companion object {
const val ITEM_SIZE_DP = 80
}
}

View File

@@ -22,6 +22,9 @@ import com.airbnb.mvrx.ViewModelContext
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.utils.DataSource
import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.settings.VectorPreferences
import io.reactivex.schedulers.Schedulers
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.NoOpMatrixCallback
import org.matrix.android.sdk.api.extensions.orFalse
@@ -29,14 +32,14 @@ import org.matrix.android.sdk.api.session.Session
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.tag.RoomTag
import io.reactivex.schedulers.Schedulers
import org.matrix.android.sdk.rx.rx
import timber.log.Timber
import javax.inject.Inject
class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
private val session: Session,
private val roomSummariesSource: DataSource<List<RoomSummary>>)
private val roomSummariesSource: DataSource<List<RoomSummary>>,
private val vectorPreferences: VectorPreferences)
: VectorViewModel<RoomListViewState, RoomListAction, RoomListViewEvents>(initialState) {
interface Factory {
@@ -64,6 +67,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
when (action) {
is RoomListAction.SelectRoom -> handleSelectRoom(action)
is RoomListAction.ToggleCategory -> handleToggleCategory(action)
is RoomListAction.ChangeCategoryMode -> handleChangeCategoryMode(action)
is RoomListAction.AcceptInvitation -> handleAcceptInvitation(action)
is RoomListAction.RejectInvitation -> handleRejectInvitation(action)
is RoomListAction.FilterWith -> handleFilter(action)
@@ -84,6 +88,10 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
this.toggle(action.category)
}
private fun handleChangeCategoryMode(action: RoomListAction.ChangeCategoryMode) = setState {
this.setMode(action.category, action.newCategoryMode)
}
private fun handleFilter(action: RoomListAction.FilterWith) {
setState {
copy(
@@ -212,34 +220,75 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
}
private fun buildRoomSummaries(rooms: List<RoomSummary>): RoomSummaries {
// Set up init size on directChats and groupRooms as they are the biggest ones
val invites = ArrayList<RoomSummary>()
val favourites = ArrayList<RoomSummary>()
val others = ArrayList<RoomSummary>(rooms.size)
val favorite = ArrayList<RoomSummary>()
val directChats = ArrayList<RoomSummary>(rooms.size)
val groupRooms = ArrayList<RoomSummary>(rooms.size)
val lowPriorities = ArrayList<RoomSummary>()
val serverNotices = ArrayList<RoomSummary>()
rooms
.filter { roomListDisplayModeFilter.test(it) }
.forEach { room ->
val tags = room.tags.map { it.name }
when {
room.membership == Membership.INVITE -> invites.add(room)
tags.contains(RoomTag.ROOM_TAG_SERVER_NOTICE) -> serverNotices.add(room)
tags.contains(RoomTag.ROOM_TAG_FAVOURITE) -> favourites.add(room)
tags.contains(RoomTag.ROOM_TAG_LOW_PRIORITY) -> lowPriorities.add(room)
room.isDirect -> directChats.add(room)
else -> groupRooms.add(room)
if (vectorPreferences.labUseTabNavigation()) {
rooms
.filter { roomListDisplayModeFilter.test(it) }
.forEach { room ->
if (room.membership == Membership.INVITE) {
invites.add(room)
} else {
val tags = room.tags.map { it.name }
if (vectorPreferences.labPinFavInTabNavigation() && tags.contains(RoomTag.ROOM_TAG_FAVOURITE)) {
favorite.add(room)
} else others.add(room)
}
}
return RoomSummaries().apply {
if (vectorPreferences.labPinFavInTabNavigation()) {
put(RoomCategory.FAVOURITE, favorite)
}
return RoomSummaries().apply {
put(RoomCategory.INVITE, invites)
put(RoomCategory.FAVOURITE, favourites)
put(RoomCategory.DIRECT, directChats)
put(RoomCategory.GROUP, groupRooms)
put(RoomCategory.LOW_PRIORITY, lowPriorities)
put(RoomCategory.SERVER_NOTICE, serverNotices)
put(RoomCategory.INVITE, invites)
put(RoomCategory.CHATS, others)
}
} else {
rooms
.filter { roomListDisplayModeFilter.test(it) }
.forEach { room ->
val tags = room.tags.map { it.name }
if (displayMode == RoomListDisplayMode.ALL) {
if (room.membership == Membership.INVITE) {
invites.add(room)
} else {
if (vectorPreferences.labPinFavInTabNavigation() && tags.contains(RoomTag.ROOM_TAG_FAVOURITE)) {
favorite.add(room)
} else others.add(room)
}
} else {
when {
room.membership == Membership.INVITE -> invites.add(room)
tags.contains(RoomTag.ROOM_TAG_SERVER_NOTICE) -> serverNotices.add(room)
tags.contains(RoomTag.ROOM_TAG_FAVOURITE) -> favorite.add(room)
tags.contains(RoomTag.ROOM_TAG_LOW_PRIORITY) -> lowPriorities.add(room)
room.isDirect -> directChats.add(room)
else -> groupRooms.add(room)
}
}
}
return RoomSummaries().apply {
put(RoomCategory.INVITE, invites)
if (displayMode == RoomListDisplayMode.ALL) {
if (vectorPreferences.labPinFavInTabNavigation()) {
put(RoomCategory.FAVOURITE, favorite)
}
put(RoomCategory.CHATS, others)
} else {
put(RoomCategory.FAVOURITE, favorite)
put(RoomCategory.DIRECT, directChats)
put(RoomCategory.GROUP, groupRooms)
put(RoomCategory.LOW_PRIORITY, lowPriorities)
put(RoomCategory.SERVER_NOTICE, serverNotices)
}
}
}
}
}

View File

@@ -17,19 +17,22 @@
package im.vector.app.features.home.room.list
import im.vector.app.features.home.HomeRoomListDataSource
import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.api.session.Session
import javax.inject.Inject
import javax.inject.Provider
class RoomListViewModelFactory @Inject constructor(private val session: Provider<Session>,
private val homeRoomListDataSource: Provider<HomeRoomListDataSource>)
private val homeRoomListDataSource: Provider<HomeRoomListDataSource>,
private val vectorPreferences: VectorPreferences)
: RoomListViewModel.Factory {
override fun create(initialState: RoomListViewState): RoomListViewModel {
return RoomListViewModel(
initialState,
session.get(),
homeRoomListDataSource.get()
homeRoomListDataSource.get(),
vectorPreferences
)
}
}

View File

@@ -37,7 +37,14 @@ data class RoomListViewState(
val isDirectRoomsExpanded: Boolean = true,
val isGroupRoomsExpanded: Boolean = true,
val isLowPriorityRoomsExpanded: Boolean = true,
val isServerNoticeRoomsExpanded: Boolean = true
val isServerNoticeRoomsExpanded: Boolean = true,
val isChatRoomsExpanded: Boolean = true,
val favouriteRoomsMode: CategoryMode = CategoryMode.Grid,
val directRoomsMode: CategoryMode = CategoryMode.List,
val groupRoomsMode: CategoryMode = CategoryMode.List,
val lowPriorityRoomsMode: CategoryMode = CategoryMode.List,
val serverNoticeRoomsMode: CategoryMode = CategoryMode.List,
val chatsRoomsMode: CategoryMode = CategoryMode.List
) : MvRxState {
constructor(args: RoomListParams) : this(displayMode = args.displayMode)
@@ -50,6 +57,26 @@ data class RoomListViewState(
RoomCategory.GROUP -> isGroupRoomsExpanded
RoomCategory.LOW_PRIORITY -> isLowPriorityRoomsExpanded
RoomCategory.SERVER_NOTICE -> isServerNoticeRoomsExpanded
// else -> true
RoomCategory.CHATS -> isChatRoomsExpanded
}
}
enum class CategoryMode {
List,
Grid
}
fun getCategoryMode(roomCategory: RoomCategory): CategoryMode {
return when (roomCategory) {
RoomCategory.INVITE -> CategoryMode.List
RoomCategory.FAVOURITE -> CategoryMode.Grid
RoomCategory.DIRECT -> directRoomsMode
RoomCategory.GROUP -> groupRoomsMode
RoomCategory.LOW_PRIORITY -> lowPriorityRoomsMode
RoomCategory.SERVER_NOTICE -> serverNoticeRoomsMode
RoomCategory.CHATS -> chatsRoomsMode
// else -> CategoryMode.List
}
}
@@ -61,6 +88,19 @@ data class RoomListViewState(
RoomCategory.GROUP -> copy(isGroupRoomsExpanded = !isGroupRoomsExpanded)
RoomCategory.LOW_PRIORITY -> copy(isLowPriorityRoomsExpanded = !isLowPriorityRoomsExpanded)
RoomCategory.SERVER_NOTICE -> copy(isServerNoticeRoomsExpanded = !isServerNoticeRoomsExpanded)
RoomCategory.CHATS -> copy(isChatRoomsExpanded = !isChatRoomsExpanded)
}
}
fun setMode(roomCategory: RoomCategory, newCategoryMode: CategoryMode): RoomListViewState {
return when (roomCategory) {
RoomCategory.INVITE -> this
RoomCategory.FAVOURITE -> copy(favouriteRoomsMode = newCategoryMode)
RoomCategory.DIRECT -> copy(directRoomsMode = newCategoryMode)
RoomCategory.GROUP -> copy(groupRoomsMode = newCategoryMode)
RoomCategory.LOW_PRIORITY -> copy(lowPriorityRoomsMode = newCategoryMode)
RoomCategory.SERVER_NOTICE -> copy(serverNoticeRoomsMode = newCategoryMode)
RoomCategory.CHATS -> copy(chatsRoomsMode = newCategoryMode)
}
}
@@ -76,6 +116,7 @@ typealias RoomSummaries = LinkedHashMap<RoomCategory, List<RoomSummary>>
enum class RoomCategory(@StringRes val titleRes: Int) {
INVITE(R.string.invitations_header),
CHATS(R.string.room_list_tabs_all),
FAVOURITE(R.string.bottom_action_favourites),
DIRECT(R.string.bottom_action_people_x),
GROUP(R.string.bottom_action_rooms),

View File

@@ -16,25 +16,44 @@
package im.vector.app.features.home.room.list
import androidx.annotation.StringRes
import android.os.Handler
import android.view.View
import com.airbnb.epoxy.EpoxyController
import im.vector.app.R
import im.vector.app.core.epoxy.helpFooterItem
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.DrawableProvider
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.resources.UserPreferencesProvider
import im.vector.app.core.ui.list.genericFooterItem
import im.vector.app.core.utils.DebouncedClickListener
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.home.room.detail.timeline.EpoxyControllerDiffHandler
import im.vector.app.features.home.room.detail.timeline.EpoxyControllerModelHandler
import im.vector.app.features.home.room.filtered.FilteredRoomFooterItem
import im.vector.app.features.home.room.filtered.filteredRoomFooterItem
import im.vector.app.features.home.room.list.grid.roomGridItem
import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
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.util.toMatrixItem
import javax.inject.Inject
class RoomSummaryController @Inject constructor(private val stringProvider: StringProvider,
private val roomSummaryItemFactory: RoomSummaryItemFactory,
private val roomListNameFilter: RoomListNameFilter,
private val userPreferencesProvider: UserPreferencesProvider
) : EpoxyController() {
private val userPreferencesProvider: UserPreferencesProvider,
private val avatarRenderer: AvatarRenderer,
private val vectorPreferences: VectorPreferences,
private val drawableProvider: DrawableProvider,
private val colorProvider: ColorProvider,
@EpoxyControllerModelHandler
private val backgroundModelHandler: Handler,
@EpoxyControllerDiffHandler
private val backgroundDiffHandler: Handler
) : EpoxyController(backgroundModelHandler, backgroundDiffHandler) {
var listener: Listener? = null
@@ -73,6 +92,7 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
.filter { it.membership == Membership.JOIN && roomListNameFilter.test(it) }
buildRoomModels(filteredSummaries,
RoomListViewState.CategoryMode.List,
viewState.roomMembershipChanges,
emptySet())
@@ -82,22 +102,53 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
private fun buildRooms(viewState: RoomListViewState) {
var showHelp = false
val roomSummaries = viewState.asyncFilteredRooms()
if (vectorPreferences.labUseTabNavigation() && vectorPreferences.labPinFavInTabNavigation()
&& !roomSummaries?.get(RoomCategory.FAVOURITE).isNullOrEmpty()) {
genericFooterItem {
id("Top Spacing")
text(" ")
spanSizeOverride { _, _, _ -> spanCount }
}
}
roomSummaries?.forEach { (category, summaries) ->
if (summaries.isEmpty()) {
return@forEach
} else {
val isExpanded = viewState.isCategoryExpanded(category)
buildRoomCategory(viewState, summaries, category.titleRes, viewState.isCategoryExpanded(category)) {
listener?.onToggleRoomCategory(category)
}
if (isExpanded) {
val mode = viewState.getCategoryMode(category)
if (vectorPreferences.labUseTabNavigation()) {
buildRoomModels(summaries,
mode,
viewState.roomMembershipChanges,
emptySet())
// Never set showHelp to true for invitation
if (category != RoomCategory.INVITE) {
showHelp = userPreferencesProvider.shouldShowLongClickOnRoomHelp()
}
} else {
buildRoomCategory(
viewState,
category,
summaries,
isExpanded,
mode,
{ newMode ->
listener?.onChangeModeRoomCategory(category, newMode)
},
{
listener?.onToggleRoomCategory(category)
}
)
if (isExpanded) {
buildRoomModels(summaries,
mode,
viewState.roomMembershipChanges,
emptySet())
// Never set showHelp to true for invitation
if (category != RoomCategory.INVITE) {
showHelp = userPreferencesProvider.shouldShowLongClickOnRoomHelp()
}
}
}
}
}
@@ -111,6 +162,7 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
helpFooterItem {
id("long_click_help")
text(stringProvider.getString(R.string.help_long_click_on_room_for_more_options))
spanSizeOverride { _, _, _ -> spanCount }
}
}
@@ -119,13 +171,16 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
id("filter_footer")
listener(listener)
currentFilter(viewState.roomFilter)
spanSizeOverride { _, _, _ -> spanCount }
}
}
private fun buildRoomCategory(viewState: RoomListViewState,
category: RoomCategory,
summaries: List<RoomSummary>,
@StringRes titleRes: Int,
isExpanded: Boolean,
mode: RoomListViewState.CategoryMode,
changeModeListener: (RoomListViewState.CategoryMode) -> Unit,
mutateExpandedState: () -> Unit) {
// TODO should add some business logic later
val unreadCount = if (summaries.isEmpty()) {
@@ -135,33 +190,73 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri
}
val showHighlighted = summaries.any { it.highlightCount > 0 }
roomCategoryItem {
id(titleRes)
title(stringProvider.getString(titleRes))
id(category.titleRes)
title(stringProvider.getString(category.titleRes))
expanded(isExpanded)
unreadNotificationCount(unreadCount)
showHighlighted(showHighlighted)
showSwitchMode(category != RoomCategory.INVITE)
mode(mode)
listener {
mutateExpandedState()
update(viewState)
}
changeModeListener(changeModeListener)
spanSizeOverride { _, _, _ -> spanCount }
}
}
private fun buildRoomModels(summaries: List<RoomSummary>,
mode: RoomListViewState.CategoryMode,
roomChangedMembershipStates: Map<String, ChangeMembershipState>,
selectedRoomIds: Set<String>) {
summaries.forEach { roomSummary ->
roomSummaryItemFactory
.create(roomSummary,
roomChangedMembershipStates,
selectedRoomIds,
listener)
.addTo(this)
when (mode) {
RoomListViewState.CategoryMode.List ->
summaries.forEach { roomSummary ->
roomSummaryItemFactory
.create(roomSummary,
roomChangedMembershipStates,
selectedRoomIds,
spanCount,
listener)
.addTo(this)
}
RoomListViewState.CategoryMode.Grid -> {
// Use breadcrumbs for the moment
summaries.forEach { roomSummary ->
roomGridItem {
id(roomSummary.roomId)
hasTypingUsers(roomSummary.typingUsers.isNotEmpty())
avatarRenderer(avatarRenderer)
matrixItem(roomSummary.toMatrixItem())
apply {
if (roomSummary.isFavorite) {
textDrawableLeft(drawableProvider.getDrawable(R.drawable.ic_star_green_24dp))
}
}
unreadNotificationCount(roomSummary.notificationCount)
showHighlighted(roomSummary.highlightCount > 0)
hasUnreadMessage(roomSummary.hasUnreadMessages)
hasDraft(roomSummary.userDrafts.isNotEmpty())
itemLongClickListener { _ ->
listener?.onRoomLongClicked(roomSummary) ?: false
}
itemClickListener(
DebouncedClickListener(View.OnClickListener { _ ->
listener?.onRoomClicked(roomSummary)
})
)
spanCount
}
}
}
}
}
interface Listener : FilteredRoomFooterItem.FilteredRoomFooterItemListener {
fun onToggleRoomCategory(roomCategory: RoomCategory)
fun onChangeModeRoomCategory(roomCategory: RoomCategory, newMode: RoomListViewState.CategoryMode)
fun onRoomClicked(room: RoomSummary)
fun onRoomLongClicked(room: RoomSummary): Boolean
fun onRejectRoomInvitation(room: RoomSummary)

View File

@@ -41,18 +41,25 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
fun create(roomSummary: RoomSummary,
roomChangeMembershipStates: Map<String, ChangeMembershipState>,
selectedRoomIds: Set<String>,
spanCount: Int,
listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> {
return when (roomSummary.membership) {
Membership.INVITE -> {
val changeMembershipState = roomChangeMembershipStates[roomSummary.roomId] ?: ChangeMembershipState.Unknown
createInvitationItem(roomSummary, changeMembershipState, listener)
createInvitationItem(roomSummary, changeMembershipState, spanCount, listener)
}
else -> createRoomItem(roomSummary, selectedRoomIds, listener?.let { it::onRoomClicked }, listener?.let { it::onRoomLongClicked })
else -> createRoomItem(roomSummary,
selectedRoomIds,
spanCount,
listener?.let { it::onRoomClicked },
listener?.let { it::onRoomLongClicked }
)
}
}
private fun createInvitationItem(roomSummary: RoomSummary,
changeMembershipState: ChangeMembershipState,
spanCount: Int,
listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> {
val secondLine = if (roomSummary.isDirect) {
roomSummary.inviterId
@@ -71,11 +78,13 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
.acceptListener { listener?.onAcceptRoomInvitation(roomSummary) }
.rejectListener { listener?.onRejectRoomInvitation(roomSummary) }
.listener { listener?.onRoomClicked(roomSummary) }
.spanSizeOverride { _, _, _ -> spanCount }
}
fun createRoomItem(
roomSummary: RoomSummary,
selectedRoomIds: Set<String>,
spanCount: Int,
onClick: ((RoomSummary) -> Unit)?,
onLongClick: ((RoomSummary) -> Boolean)?
): VectorEpoxyModel<*> {
@@ -113,5 +122,6 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
onClick?.invoke(roomSummary)
})
)
.spanSizeOverride { _, _, _ -> spanCount }
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.home.room.list.grid
import android.graphics.drawable.Drawable
import android.view.HapticFeedbackConstants
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.list.UnreadCounterBadgeView
import org.matrix.android.sdk.api.util.MatrixItem
@EpoxyModelClass(layout = R.layout.item_room_grid)
abstract class RoomGridItem : VectorEpoxyModel<RoomGridItem.Holder>() {
@EpoxyAttribute var hasTypingUsers: Boolean = false
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute lateinit var matrixItem: MatrixItem
@EpoxyAttribute var unreadNotificationCount: Int = 0
@EpoxyAttribute var showHighlighted: Boolean = false
@EpoxyAttribute var hasUnreadMessage: Boolean = false
@EpoxyAttribute var hasDraft: Boolean = false
@EpoxyAttribute var textDrawableLeft: Drawable? = null
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: View.OnClickListener? = null
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemLongClickListener: View.OnLongClickListener? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.rootView.setOnClickListener(itemClickListener)
holder.rootView.setOnLongClickListener {
it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
itemLongClickListener?.onLongClick(it) ?: false
}
holder.unreadIndentIndicator.isVisible = hasUnreadMessage
avatarRenderer.render(matrixItem, holder.avatarImageView)
holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted))
holder.draftIndentIndicator.isVisible = hasDraft
holder.typingIndicator.isVisible = hasTypingUsers
holder.roomName.text = if (textDrawableLeft != null) "⭐️ ${matrixItem.getBestName()}" else matrixItem.getBestName()
// TODO this drawable don't work
// holder.roomName.setCompoundDrawables(textDrawableLeft, null, null, null)
}
override fun unbind(holder: Holder) {
holder.rootView.setOnClickListener(null)
super.unbind(holder)
}
class Holder : VectorEpoxyHolder() {
val unreadCounterBadgeView by bind<UnreadCounterBadgeView>(R.id.itemRoomGridUnreadCounterBadgeView)
val unreadIndentIndicator by bind<View>(R.id.itemRoomGridUnreadIndicator)
val draftIndentIndicator by bind<View>(R.id.itemRoomGridDraftBadge)
val typingIndicator by bind<View>(R.id.itemRoomGridTypingView)
val avatarImageView by bind<ImageView>(R.id.itemRoomGridImageView)
val roomName by bind<TextView>(R.id.itemRoomGridRoomName)
val rootView by bind<ViewGroup>(R.id.itemRoomGridRoot)
}
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.home.room.list.tabs
import im.vector.app.core.platform.VectorViewModelAction
sealed class RoomListTabsAction : VectorViewModelAction

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.home.room.list.tabs
import android.os.Bundle
import android.view.View
import androidx.viewpager2.widget.ViewPager2
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import com.google.android.material.tabs.TabLayoutMediator
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.features.home.HomeActivitySharedAction
import im.vector.app.features.home.HomeSharedActionViewModel
import im.vector.app.features.settings.VectorPreferences
import kotlinx.android.synthetic.main.fragment_room_list_tabs.*
import timber.log.Timber
import javax.inject.Inject
class RoomListTabsFragment @Inject constructor(
private val viewModelFactory: RoomListTabsViewModel.Factory,
private val vectorPreferences: VectorPreferences
) : VectorBaseFragment(), RoomListTabsViewModel.Factory by viewModelFactory {
private val viewModel: RoomListTabsViewModel by fragmentViewModel()
private lateinit var pagerAdapter: RoomListTabsPagerAdapter
private lateinit var sharedActionViewModel: HomeSharedActionViewModel
override fun getLayoutResId() = R.layout.fragment_room_list_tabs
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java)
pagerAdapter = RoomListTabsPagerAdapter(this, requireContext(), vectorPreferences)
viewPager.adapter = pagerAdapter
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
val item = pagerAdapter.getTabs()[position]
tab.text = getString(item.titleRes).toLowerCase().capitalize()
}.attach()
val onPageChangeListener: ViewPager2.OnPageChangeCallback = object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
val item = pagerAdapter.getTabs()[position]
sharedActionViewModel.post(HomeActivitySharedAction.OnDisplayModeSelected(item))
}
}
viewPager.registerOnPageChangeCallback(onPageChangeListener)
}
override fun onResume() {
// Tmp if the labs preference did change
pagerAdapter.notifyDataSetChanged()
super.onResume()
}
override fun invalidate() = withState(viewModel) { state ->
Timber.v("Invalidate state: $state")
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.home.room.list.tabs
import android.content.Context
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.airbnb.mvrx.MvRx
import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.home.room.list.RoomListFragment
import im.vector.app.features.home.room.list.RoomListParams
import im.vector.app.features.settings.VectorPreferences
class RoomListTabsPagerAdapter(private val fragment: Fragment,
private val context: Context,
private val vectorPreferences: VectorPreferences) : FragmentStateAdapter(fragment) {
// companion object {
// val TABS = listOf(
// RoomListDisplayMode.ALL,
// RoomListDisplayMode.FAVORITES,
// RoomListDisplayMode.NOTIFICATIONS,
// RoomListDisplayMode.ROOMS,
// RoomListDisplayMode.PEOPLE,
// RoomListDisplayMode.INVITES,
// RoomListDisplayMode.LOW_PRIORITY
// )
// }
fun getTabs() : List<RoomListDisplayMode> {
return ArrayList<RoomListDisplayMode>().apply {
add(RoomListDisplayMode.ALL)
if (!vectorPreferences.labPinFavInTabNavigation()) {
add(RoomListDisplayMode.FAVORITES)
}
add(RoomListDisplayMode.NOTIFICATIONS)
add(RoomListDisplayMode.ROOMS)
add(RoomListDisplayMode.PEOPLE)
add(RoomListDisplayMode.INVITES)
add(RoomListDisplayMode.LOW_PRIORITY)
}
}
override fun getItemCount() = getTabs().count()
override fun createFragment(position: Int): Fragment {
val roomListFragment = fragment.childFragmentManager.fragmentFactory.instantiate(context.classLoader, RoomListFragment::class.java.name)
val displayMode = getTabs()[position]
val params = RoomListParams(displayMode)
return roomListFragment.apply {
arguments = Bundle().apply { putParcelable(MvRx.KEY_ARG, params) }
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.home.room.list.tabs
import com.airbnb.mvrx.ActivityViewModelContext
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import timber.log.Timber
class RoomListTabsViewModel @AssistedInject constructor(@Assisted initialState: RoomListTabsViewState)
: VectorViewModel<RoomListTabsViewState, RoomListTabsAction, EmptyViewEvents>(initialState) {
@AssistedInject.Factory
interface Factory {
fun create(initialState: RoomListTabsViewState): RoomListTabsViewModel
}
companion object : MvRxViewModelFactory<RoomListTabsViewModel, RoomListTabsViewState> {
@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: RoomListTabsViewState): RoomListTabsViewModel? {
val factory = when (viewModelContext) {
is FragmentViewModelContext -> viewModelContext.fragment as? Factory
is ActivityViewModelContext -> viewModelContext.activity as? Factory
}
return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
}
}
override fun handle(action: RoomListTabsAction) {
Timber.v("Action $action not handled")
}
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.home.room.list.tabs
import com.airbnb.mvrx.MvRxState
data class RoomListTabsViewState(val noop: Boolean = false) : MvRxState

View File

@@ -154,6 +154,9 @@ class VectorPreferences @Inject constructor(private val context: Context) {
private const val SETTINGS_LABS_MERGE_E2E_ERRORS = "SETTINGS_LABS_MERGE_E2E_ERRORS"
const val SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB = "SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB"
const val SETTINGS_LABS_USE_TOP_TAB_NAVIGATION = "SETTINGS_LABS_USE_TOP_TAB_NAVIGATION"
const val SETTINGS_LABS_PIN_FAVORITE = "SETTINGS_LABS_PIN_FAVORITE"
// analytics
const val SETTINGS_USE_ANALYTICS_KEY = "SETTINGS_USE_ANALYTICS_KEY"
@@ -289,6 +292,14 @@ class VectorPreferences @Inject constructor(private val context: Context) {
return defaultPrefs.getBoolean(SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB, false)
}
fun labUseTabNavigation(): Boolean {
return defaultPrefs.getBoolean(SETTINGS_LABS_USE_TOP_TAB_NAVIGATION, false)
}
fun labPinFavInTabNavigation(): Boolean {
return defaultPrefs.getBoolean(SETTINGS_LABS_PIN_FAVORITE, false)
}
fun failFast(): Boolean {
return BuildConfig.DEBUG || (developerMode() && defaultPrefs.getBoolean(SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY, false))
}

View File

@@ -52,7 +52,13 @@ class IncomingShareController @Inject constructor(private val roomSummaryItemFac
} else {
roomSummaries.forEach { roomSummary ->
roomSummaryItemFactory
.createRoomItem(roomSummary, data.selectedRoomIds, callback?.let { it::onRoomClicked }, callback?.let { it::onRoomLongClicked })
.createRoomItem(
roomSummary,
data.selectedRoomIds,
1,
callback?.let { it::onRoomClicked },
callback?.let { it::onRoomLongClicked }
)
.addTo(this)
}
}

View File

@@ -30,5 +30,7 @@ interface UiStateRepository {
fun getDisplayMode(): RoomListDisplayMode
// fun getHomeDisplayMode(): HomeDisplayMode
fun storeDisplayMode(displayMode: RoomListDisplayMode)
}

View File

@@ -0,0 +1,9 @@
<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="M2,20.9241V9.4249C2,8.8381 2.2576,8.281 2.7047,7.901L10.7047,1.101C11.4516,0.4662 12.5484,0.4662 13.2953,1.101L21.2953,7.901C21.7424,8.281 22,8.836 22,9.4227V20.9634C22,22.6349 20.6392,23.9836 18.9678,23.9687C15.4826,23.9377 9.654,23.9012 5.0728,23.9489C3.3867,23.9664 2,22.6103 2,20.9241Z"
android:fillColor="#03B381"/>
</vector>

View File

@@ -0,0 +1,9 @@
<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="M18.4,4H5.6C4.72,4 4,4.72 4,5.6V18.4C4,19.28 4.72,20 5.6,20H18.4C19.28,20 20,19.28 20,18.4V5.6C20,4.72 19.28,4 18.4,4ZM8.8,18.4H5.6V15.2H8.8V18.4ZM8.8,13.6H5.6V10.4H8.8V13.6ZM8.8,8.8H5.6V5.6H8.8V8.8ZM13.6,18.4H10.4V15.2H13.6V18.4ZM13.6,13.6H10.4V10.4H13.6V13.6ZM13.6,8.8H10.4V5.6H13.6V8.8ZM18.4,18.4H15.2V15.2H18.4V18.4ZM18.4,13.6H15.2V10.4H18.4V13.6ZM18.4,8.8H15.2V5.6H18.4V8.8Z"
android:fillColor="#000000"/>
</vector>

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="M4,5C4,5.5523 4.4477,6 5,6C5.5523,6 6,5.5523 6,5C6,4.4477 5.5523,4 5,4C4.4477,4 4,4.4477 4,5ZM8,5C8,5.5523 8.4477,6 9,6H19C19.5523,6 20,5.5523 20,5C20,4.4477 19.5523,4 19,4H9C8.4477,4 8,4.4477 8,5ZM4,9.5C4,10.0523 4.4477,10.5 5,10.5C5.5523,10.5 6,10.0523 6,9.5C6,8.9477 5.5523,8.5 5,8.5C4.4477,8.5 4,8.9477 4,9.5ZM8,9.5C8,10.0523 8.4477,10.5 9,10.5H19C19.5523,10.5 20,10.0523 20,9.5C20,8.9477 19.5523,8.5 19,8.5H9C8.4477,8.5 8,8.9477 8,9.5ZM4,19C4,19.5523 4.4477,20 5,20C5.5523,20 6,19.5523 6,19C6,18.4477 5.5523,18 5,18C4.4477,18 4,18.4477 4,19ZM8,19C8,19.5523 8.4477,20 9,20H19C19.5523,20 20,19.5523 20,19C20,18.4477 19.5523,18 19,18H9C8.4477,18 8,18.4477 8,19ZM6,14.5C6,15.0523 5.5523,15.5 5,15.5C4.4477,15.5 4,15.0523 4,14.5C4,13.9477 4.4477,13.5 5,13.5C5.5523,13.5 6,13.9477 6,14.5ZM20,14.5C20,15.0523 19.5523,15.5 19,15.5H9C8.4477,15.5 8,15.0523 8,14.5C8,13.9477 8.4477,13.5 9,13.5H19C19.5523,13.5 20,13.9477 20,14.5Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
</vector>

View File

@@ -0,0 +1,18 @@
<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="M19.2,21.6009C22.1147,19.4116 24,15.926 24,12C24,5.3726 18.6274,0 12,0C5.3726,0 0,5.3726 0,12C0,15.926 1.8853,19.4116 4.8,21.6009V21.6008C6.8056,23.1073 9.2985,24 12,24C14.7015,24 17.1944,23.1073 19.2,21.6009ZM12,12.6C13.9882,12.6 15.6,10.8539 15.6,8.7C15.6,6.5461 13.9882,4.8 12,4.8C10.0118,4.8 8.4,6.5461 8.4,8.7C8.4,10.8539 10.0118,12.6 12,12.6ZM12,21.6C14.5944,21.6 16.9484,20.5709 18.6761,18.8986C17.6076,16.2607 15.0211,14.4 12,14.4C8.9789,14.4 6.3924,16.2607 5.3239,18.8986C7.0516,20.5708 9.4056,21.6 12,21.6Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
<group>
<clip-path
android:pathData="M19.2,21.6009C22.1147,19.4116 24,15.926 24,12C24,5.3726 18.6274,0 12,0C5.3726,0 0,5.3726 0,12C0,15.926 1.8853,19.4116 4.8,21.6009V21.6008C6.8056,23.1073 9.2985,24 12,24C14.7015,24 17.1944,23.1073 19.2,21.6009ZM12,12.6C13.9882,12.6 15.6,10.8539 15.6,8.7C15.6,6.5461 13.9882,4.8 12,4.8C10.0118,4.8 8.4,6.5461 8.4,8.7C8.4,10.8539 10.0118,12.6 12,12.6ZM12,21.6C14.5944,21.6 16.9484,20.5709 18.6761,18.8986C17.6076,16.2607 15.0211,14.4 12,14.4C8.9789,14.4 6.3924,16.2607 5.3239,18.8986C7.0516,20.5708 9.4056,21.6 12,21.6Z"
android:fillType="evenOdd"/>
<path
android:pathData="M19.2,21.6009L20.4012,23.2L20.4012,23.2L19.2,21.6009ZM4.8,21.6009L3.5989,23.2L6.8,25.6045V21.6009H4.8ZM4.8,21.6008L6.0012,20.0017L2.8,17.5972V21.6008H4.8ZM18.6761,18.8986L20.067,20.3356L21.0361,19.3977L20.5298,18.1477L18.6761,18.8986ZM5.3239,18.8986L3.4702,18.1477L2.9639,19.3977L3.933,20.3356L5.3239,18.8986ZM20.4012,23.2C23.7971,20.6492 26,16.5816 26,12H22C22,15.2703 20.4323,18.1739 17.9988,20.0017L20.4012,23.2ZM26,12C26,4.268 19.732,-2 12,-2V2C17.5228,2 22,6.4771 22,12H26ZM12,-2C4.268,-2 -2,4.268 -2,12H2C2,6.4771 6.4771,2 12,2V-2ZM-2,12C-2,16.5816 0.2029,20.6492 3.5989,23.2L6.0012,20.0017C3.5677,18.1739 2,15.2703 2,12H-2ZM2.8,21.6008V21.6009H6.8V21.6008H2.8ZM12,22C9.7465,22 7.6723,21.257 6.0012,20.0017L3.5988,23.2C5.9389,24.9577 8.8506,26 12,26V22ZM17.9988,20.0017C16.3277,21.257 14.2535,22 12,22V26C15.1494,26 18.0611,24.9577 20.4012,23.2L17.9988,20.0017ZM13.6,8.7C13.6,9.9031 12.7359,10.6 12,10.6V14.6C15.2405,14.6 17.6,11.8047 17.6,8.7H13.6ZM12,6.8C12.7359,6.8 13.6,7.4969 13.6,8.7H17.6C17.6,5.5953 15.2405,2.8 12,2.8V6.8ZM10.4,8.7C10.4,7.4969 11.2641,6.8 12,6.8V2.8C8.7595,2.8 6.4,5.5953 6.4,8.7H10.4ZM12,10.6C11.2641,10.6 10.4,9.9031 10.4,8.7H6.4C6.4,11.8047 8.7595,14.6 12,14.6V10.6ZM17.2851,17.4615C15.9154,18.7873 14.0547,19.6 12,19.6V23.6C15.1341,23.6 17.9814,22.3544 20.067,20.3356L17.2851,17.4615ZM12,16.4C14.1788,16.4 16.0492,17.7406 16.8223,19.6494L20.5298,18.1477C19.166,14.7808 15.8634,12.4 12,12.4V16.4ZM7.1777,19.6494C7.9508,17.7406 9.8212,16.4 12,16.4V12.4C8.1366,12.4 4.834,14.7808 3.4702,18.1477L7.1777,19.6494ZM12,19.6C9.9454,19.6 8.0846,18.7873 6.7149,17.4615L3.933,20.3356C6.0186,22.3544 8.8659,23.6 12,23.6V19.6Z"
android:fillColor="#000000"/>
</group>
</vector>

View File

@@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:pathData="M5.0426,19.9701C7.5512,19.9701 9.5849,17.9514 9.5849,15.4612C9.5849,12.971 7.5512,10.9523 5.0426,10.9523C2.5339,10.9523 0.5002,12.971 0.5002,15.4612C0.5002,16.1585 0.6597,16.8188 0.9443,17.4081L0.5571,18.6574C0.3188,19.4261 1.0399,20.147 1.8085,19.9085L3.0615,19.5199C3.6602,19.8084 4.3324,19.9701 5.0426,19.9701Z"
android:strokeWidth="1"
android:fillColor="#0DBD8B"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
<path
android:pathData="M10.322,18.2236C10.755,17.4092 11,16.4819 11,15.4981C11,12.2555 8.3384,9.6269 5.0551,9.6269C3.5162,9.6269 2.1139,10.2044 1.0582,11.152C0.9409,10.5766 0.8794,9.981 0.8794,9.371C0.8794,4.4717 4.8496,0.5 9.7472,0.5C14.6447,0.5 18.6149,4.4717 18.6149,9.371C18.6149,10.7434 18.3034,12.043 17.7472,13.2029L19.042,17.4126C19.2786,18.1818 18.5559,18.9011 17.7878,18.661L13.6142,17.3563C12.6083,17.8447 11.4962,18.1485 10.322,18.2236Z"
android:strokeWidth="1"
android:fillColor="#0DBD8B"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>

View File

@@ -1,118 +1,196 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/groupToolbar"
style="@style/VectorToolbarStyle"
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:elevation="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
android:layout_height="wrap_content"
app:elevation="0dp">
<LinearLayout
<androidx.appcompat.widget.Toolbar
android:id="@+id/groupToolbar"
style="@style/VectorToolbarStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal">
android:layout_height="?attr/actionBarSize"
android:elevation="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_scrollFlags="scroll|enterAlways">
<ImageView
android:id="@+id/groupToolbarAvatarImageView"
android:layout_width="32dp"
android:layout_height="32dp"
android:contentDescription="@string/a11y_open_drawer"
tools:src="@tools:sample/avatars" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/groupToolbarTitleView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?riotx_text_primary"
android:textSize="20sp"
android:textStyle="bold"
tools:text="@tools:sample/lorem/random" />
<ImageView
android:id="@+id/groupToolbarAvatarImageView"
android:layout_width="32dp"
android:layout_height="32dp"
android:contentDescription="@string/a11y_open_drawer"
tools:src="@tools:sample/avatars" />
</LinearLayout>
<TextView
android:id="@+id/groupToolbarTitleView"
android:layout_width="match_parent"
android:layout_height="32dp"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:textColor="?riotx_text_primary"
android:textStyle="bold"
app:autoSizeMaxTextSize="16sp"
app:autoSizeMinTextSize="10sp"
app:autoSizeStepGranularity="1sp"
app:autoSizeTextType="uniform"
tools:text="@tools:sample/full_names" />
</androidx.appcompat.widget.Toolbar>
</LinearLayout>
<im.vector.app.features.sync.widget.SyncStateView
android:id="@+id/syncStateView"
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/groupToolbar" />
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<im.vector.app.core.ui.views.KeysBackupBanner
android:id="@+id/homeKeysBackupBanner"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?riotx_keys_backup_banner_accent_color"
android:minHeight="67dp"
android:visibility="gone"
tools:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/syncStateView" />
<im.vector.app.features.sync.widget.SyncStateView
android:id="@+id/syncStateView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<im.vector.app.core.ui.views.ActiveCallView
android:id="@+id/activeCallView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/homeKeysBackupBanner"
tools:visibility="visible" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/roomListContainer"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="?riotx_header_panel_background"
app:layout_constraintBottom_toTopOf="@+id/bottomNavigationView"
app:layout_constraintTop_toBottomOf="@+id/activeCallView" />
<androidx.cardview.widget.CardView
android:id="@+id/activeCallPiPWrap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:clickable="true"
android:focusable="true"
app:cardCornerRadius="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/activeCallView">
<org.webrtc.SurfaceViewRenderer
android:id="@+id/activeCallPiP"
android:layout_width="120dp"
android:layout_height="120dp"
<im.vector.app.core.ui.views.KeysBackupBanner
android:id="@+id/homeKeysBackupBanner"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?riotx_keys_backup_banner_accent_color"
android:minHeight="67dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/syncStateView"
tools:visibility="visible" />
</androidx.cardview.widget.CardView>
<im.vector.app.core.ui.views.ActiveCallView
android:id="@+id/activeCallView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/homeKeysBackupBanner"
tools:visibility="visible" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/roomListContainer"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="?riotx_header_panel_background"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/activeCallView" />
<androidx.cardview.widget.CardView
android:id="@+id/activeCallPiPWrap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:clickable="true"
android:focusable="true"
app:cardCornerRadius="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/activeCallView">
<org.webrtc.SurfaceViewRenderer
android:id="@+id/activeCallPiP"
android:layout_width="120dp"
android:layout_height="120dp"
android:visibility="gone"
tools:visibility="visible" />
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
<im.vector.app.features.home.room.list.widget.FabMenuView
android:id="@+id/createChatFabMenu"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="48dp"
android:visibility="gone"
app:layoutDescription="@xml/motion_scene_fab_menu"
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
tools:layout_editor_absoluteX="1dp"
tools:layout_editor_absoluteY="-56dp"
tools:showPaths="true"
tools:visibility="visible" />
<!-- TODO check accessibilityTraversalBefore as we changed layout -->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/createChatRoomButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="16dp"
android:layout_marginBottom="64dp"
android:accessibilityTraversalBefore="@+id/roomListView"
android:contentDescription="@string/a11y_create_direct_message"
android:scaleType="center"
android:src="@drawable/ic_fab_add_chat"
android:visibility="gone"
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
app:maxImageSize="34dp"
tools:layout_marginEnd="80dp"
tools:visibility="visible" />
<!-- TODO check accessibilityTraversalBefore as we changed layout -->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/createGroupRoomButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="16dp"
android:layout_marginBottom="64dp"
android:accessibilityTraversalBefore="@+id/roomListView"
android:contentDescription="@string/a11y_create_room"
android:src="@drawable/ic_fab_add_room"
android:visibility="gone"
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
app:maxImageSize="32dp"
tools:layout_marginEnd="144dp"
tools:visibility="visible" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigationView"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="?riotx_background"
app:itemIconSize="20dp"
app:itemIconTint="@color/bottom_navigation_icon_tint_selector"
app:labelVisibilityMode="unlabeled"
app:labelVisibilityMode="labeled"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/roomListContainer"
app:layout_insetEdge="bottom"
app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior"
app:menu="@menu/home_bottom_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -5,7 +5,7 @@
android:id="@+id/stateView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?riotx_header_panel_background">
android:background="?riotx_background">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/roomListView"
@@ -13,44 +13,4 @@
android:layout_height="match_parent"
android:overScrollMode="always" />
<im.vector.app.features.home.room.list.widget.FabMenuView
android:id="@+id/createChatFabMenu"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
app:layoutDescription="@xml/motion_scene_fab_menu"
tools:showPaths="true"
tools:visibility="visible" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/createChatRoomButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:accessibilityTraversalBefore="@+id/roomListView"
android:contentDescription="@string/a11y_create_direct_message"
android:scaleType="center"
android:src="@drawable/ic_fab_add_chat"
android:visibility="gone"
app:maxImageSize="34dp"
tools:layout_marginEnd="80dp"
tools:visibility="visible" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/createGroupRoomButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:accessibilityTraversalBefore="@+id/roomListView"
android:contentDescription="@string/a11y_create_room"
android:src="@drawable/ic_fab_add_room"
android:visibility="gone"
app:maxImageSize="32dp"
tools:layout_marginEnd="144dp"
tools:visibility="visible" />
</im.vector.app.core.platform.StateView>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
app:tabMode="scrollable"
app:tabBackground="?attr/riotx_background"
style="@style/Vector.Widget.TabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>

View File

@@ -26,20 +26,20 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="12dp"
android:layout_marginTop="10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/roomAvatarImageView"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_width="46dp"
android:layout_height="46dp"
tools:src="@tools:sample/avatars" />
<ImageView
android:id="@+id/roomAvatarCheckedImageView"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_width="46dp"
android:layout_height="46dp"
android:scaleType="centerInside"
android:src="@drawable/ic_material_done"
android:tint="@android:color/white"
@@ -54,7 +54,7 @@
android:src="@drawable/ic_warning_badge"
app:layout_constraintCircle="@id/roomAvatarContainer"
app:layout_constraintCircleAngle="45"
app:layout_constraintCircleRadius="30dp"
app:layout_constraintCircleRadius="20dp"
tools:ignore="MissingConstraints" />
<ImageView
@@ -63,7 +63,7 @@
android:layout_height="24dp"
app:layout_constraintCircle="@id/roomAvatarContainer"
app:layout_constraintCircleAngle="135"
app:layout_constraintCircleRadius="28dp"
app:layout_constraintCircleRadius="20dp"
tools:ignore="MissingConstraints"
tools:src="@drawable/ic_shield_trusted" />
@@ -71,7 +71,7 @@
<Space
android:id="@+id/roomAvatarBottomSpace"
android:layout_width="0dp"
android:layout_height="12dp"
android:layout_height="10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/roomAvatarContainer"
tools:layout_marginStart="20dp" />
@@ -81,13 +81,13 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginTop="12dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="8dp"
android:duplicateParentState="true"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?riotx_text_primary"
android:textSize="15sp"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@+id/roomDraftBadge"
@@ -138,7 +138,7 @@
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:textColor="?riotx_text_secondary"
android:textSize="15sp"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="@+id/roomNameView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/roomNameView"
@@ -153,7 +153,7 @@
android:ellipsize="end"
android:maxLines="2"
android:textColor="?riotx_text_secondary"
android:textSize="15sp"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/roomNameView"
app:layout_constraintTop_toBottomOf="@+id/roomNameView"
@@ -198,7 +198,7 @@
android:background="?riotx_header_panel_border_mobile"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintStart_toStartOf="@+id/roomNameView"
app:layout_constraintTop_toBottomOf="@+id/roomBottomBarrier" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/roomCategoryRootView"
android:layout_width="match_parent"
@@ -8,19 +9,15 @@
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="@dimen/layout_horizontal_margin"
android:paddingBottom="4dp">
android:paddingEnd="2dp">
<TextView
android:id="@+id/roomCategoryTitleView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
tools:drawableEnd="@drawable/ic_expand_more_white"
android:layout_marginTop="12dp"
android:layout_marginBottom="4dp"
android:drawableTint="?riotx_text_secondary"
android:ellipsize="end"
android:gravity="center_vertical"
@@ -28,12 +25,17 @@
android:textColor="?riotx_text_secondary"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:drawableEnd="@drawable/ic_expand_more_white"
tools:text="@string/room_participants_header_direct_chats" />
<im.vector.app.features.home.room.list.UnreadCounterBadgeView
android:id="@+id/roomCategoryUnreadCounterBadgeView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:gravity="center"
android:minWidth="16dp"
android:minHeight="16dp"
@@ -41,7 +43,21 @@
android:paddingEnd="4dp"
android:textColor="@android:color/white"
android:textSize="10sp"
app:layout_constraintBottom_toBottomOf="@+id/roomCategoryTitleView"
app:layout_constraintStart_toEndOf="@+id/roomCategoryTitleView"
app:layout_constraintTop_toTopOf="@+id/roomCategoryTitleView"
tools:background="@drawable/bg_unread_highlight"
tools:text="24" />
</LinearLayout>
<ImageView
android:id="@+id/roomCategoryMode"
android:layout_width="48dp"
android:layout_height="0dp"
android:scaleType="center"
android:tint="?riotx_text_secondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_mode_list_24dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/itemRoomGridRoot"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?riotx_background"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground">
<ImageView
android:id="@+id/itemRoomGridUnreadIndicator"
android:layout_width="60dp"
android:layout_height="60dp"
android:src="@drawable/circle"
android:tint="?attr/colorAccent"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/itemRoomGridImageView"
app:layout_constraintEnd_toEndOf="@+id/itemRoomGridImageView"
app:layout_constraintStart_toStartOf="@+id/itemRoomGridImageView"
app:layout_constraintTop_toTopOf="@+id/itemRoomGridImageView"
tools:visibility="visible" />
<ImageView
android:id="@+id/itemRoomGridImageView"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginStart="12dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
<im.vector.app.features.home.room.list.UnreadCounterBadgeView
android:id="@+id/itemRoomGridUnreadCounterBadgeView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:minWidth="18dp"
android:minHeight="18dp"
android:textColor="@android:color/white"
android:textSize="11sp"
android:visibility="gone"
app:layout_constraintCircle="@+id/itemRoomGridImageView"
app:layout_constraintCircleAngle="45"
app:layout_constraintCircleRadius="28dp"
tools:background="@drawable/bg_unread_highlight"
tools:ignore="MissingConstraints"
tools:text="24"
tools:visibility="visible" />
<TextView
android:id="@+id/itemRoomGridTypingView"
android:layout_width="20dp"
android:layout_height="20dp"
android:background="@drawable/bg_breadcrumbs_typing"
android:gravity="center"
android:text="@string/ellipsis"
android:textColor="@android:color/white"
android:textSize="11sp"
android:textStyle="bold"
android:visibility="gone"
app:layout_constraintCircle="@+id/itemRoomGridImageView"
app:layout_constraintCircleAngle="135"
app:layout_constraintCircleRadius="28dp"
tools:ignore="MissingConstraints"
tools:visibility="visible" />
<ImageView
android:id="@+id/itemRoomGridDraftBadge"
android:layout_width="20dp"
android:layout_height="20dp"
android:background="@drawable/circle"
android:padding="3dp"
android:src="@drawable/ic_edit"
android:visibility="gone"
app:layout_constraintCircle="@+id/itemRoomGridImageView"
app:layout_constraintCircleAngle="225"
app:layout_constraintCircleRadius="28dp"
tools:ignore="MissingConstraints"
tools:visibility="visible" />
<TextView
android:id="@+id/itemRoomGridRoomName"
android:layout_width="76dp"
android:layout_height="wrap_content"
android:layout_margin="2dp"
android:ellipsize="end"
android:gravity="center"
android:lines="2"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/itemRoomGridImageView"
tools:text="@sample/matrix.json/data/roomName" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Toolbar
android:layout_width="wrap_content"
android:layout_height="match_parent"/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<!-- app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior"-->
<com.google.android.material.bottomnavigation.BottomNavigationView
android:layout_width="match_parent"
app:menu="@menu/home"
android:layout_gravity="bottom"
android:layout_height="wrap_content"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -1,11 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/bottom_action_chats"
android:enabled="true"
android:icon="@drawable/ic_home_bottom_house"
android:title="@string/bottom_action_home" />
<item
android:id="@+id/bottom_action_favourites"
android:enabled="true"
android:icon="@drawable/ic_star_24dp"
android:title="@string/bottom_action_favourites" />
<item
android:id="@+id/bottom_action_people"
android:enabled="true"
android:icon="@drawable/ic_home_bottom_chat"
android:title="@string/bottom_action_people_x" />
android:title="@string/bottom_action_people_short" />
<item
android:id="@+id/bottom_action_rooms"
@@ -13,6 +29,7 @@
android:icon="@drawable/ic_home_bottom_group"
android:title="@string/bottom_action_rooms" />
<item
android:id="@+id/bottom_action_notification"
android:enabled="true"
@@ -20,4 +37,12 @@
android:title="@string/bottom_action_notification"
android:visible="false" />
<!-- <item-->
<!-- android:id="@+id/bottom_action_you"-->
<!-- android:enabled="true"-->
<!-- android:icon="@drawable/ic_people"-->
<!-- android:title="@string/home_bottom_tab_you"-->
<!-- android:visible="false" />-->
</menu>

View File

@@ -144,11 +144,15 @@
<!-- Bottom navigation buttons -->
<string name="bottom_action_home">Home</string>
<string name="bottom_action_notification">Notifications</string>
<string name="bottom_action_notification">Unread</string>
<string name="bottom_action_favourites">Favourites</string>
<string name="bottom_action_people">People</string>
<string name="bottom_action_rooms">Rooms</string>
<string name="bottom_action_groups">Communities</string>
<string name="room_list_tabs_all">Chats</string>
<string name="home_bottom_tab_chats">Chats</string>
<string name="home_bottom_tab_you">You</string>
<!-- Home screen -->
<string name="home_filter_placeholder_home">Filter room names</string>
@@ -1700,6 +1704,7 @@
<string name="settings_labs_show_hidden_events_in_timeline">Show hidden events in timeline</string>
<string name="bottom_action_people_x">Direct Messages</string>
<string name="bottom_action_people_short">DMs</string>
<string name="send_file_step_idle">Waiting…</string>
<string name="send_file_step_encrypting_thumbnail">Encrypting thumbnail…</string>
@@ -1727,6 +1732,9 @@
<string name="labs_swipe_to_reply_in_timeline">Enable swipe to reply in timeline</string>
<string name="labs_merge_e2e_in_timeline">Merge failed to decrypt message in timeline</string>
<string name="labs_show_unread_notifications_as_tab">Add a dedicated tab for unread notifications on main screen.</string>
<string name="labs_experimental_nav_title">Experimental Navigation</string>
<string name="labs_experimental_top_tabs">Use top tabs</string>
<string name="labs_experimental_pin_favorites">Pin favorites</string>
<string name="link_copied_to_clipboard">Link copied to clipboard</string>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Vector.Widget.TabLayout" parent="Widget.Design.TabLayout">
<item name="tabTextAppearance">@style/TextAppearance.Vector.Tabs</item>
</style>
</resources>

View File

@@ -3,4 +3,5 @@
<style name="Vector.PopupMenu" parent="Vector.PopupMenuBase" />
</resources>

View File

@@ -60,4 +60,9 @@
<item name="android:textColor">?riotx_android_secondary</item>
</style>
<style name="TextAppearance.Vector.Tabs" parent="TextAppearance.Design.Tab">
<item name="textAllCaps">false</item>
<item name="android:textAllCaps">false</item>
</style>
</resources>

View File

@@ -49,7 +49,22 @@
<im.vector.app.core.preference.VectorSwitchPreference
android:defaultValue="false"
android:key="SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB"
android:title="@string/labs_show_unread_notifications_as_tab" />
android:title="@string/labs_experimental_nav_title" />
<im.vector.app.core.preference.VectorPreferenceCategory android:title="@string/labs_experimental_nav_title">
<im.vector.app.core.preference.VectorSwitchPreference
android:defaultValue="false"
android:key="SETTINGS_LABS_USE_TOP_TAB_NAVIGATION"
android:title="@string/labs_experimental_top_tabs" />
<im.vector.app.core.preference.VectorSwitchPreference
android:defaultValue="false"
android:dependency="SETTINGS_LABS_USE_TOP_TAB_NAVIGATION"
android:key="SETTINGS_LABS_PIN_FAVORITE"
android:title="@string/labs_experimental_pin_favorites" />
</im.vector.app.core.preference.VectorPreferenceCategory>
<!--</im.vector.app.core.preference.VectorPreferenceCategory>-->
</androidx.preference.PreferenceScreen>