From 0629cae183f3fba6591986cee4f4529d16b652e9 Mon Sep 17 00:00:00 2001 From: Nikita Fedrunov <66663241+fedrunov@users.noreply.github.com> Date: Fri, 19 Aug 2022 17:53:48 +0200 Subject: [PATCH] added dialog to change app layout settings (#6840) --- changelog.d/6506.wip | 1 + .../vector/app/features/home/HomeActivity.kt | 10 +++ .../list/home/HomeLayoutPreferencesStore.kt | 70 +++++++++++++++++ .../room/list/home/HomeRoomListFragment.kt | 15 +++- .../room/list/home/HomeRoomListViewModel.kt | 78 ++++++++++++------- .../home/room/list/home/HomeRoomSection.kt | 4 +- .../filter/HomeFilteredRoomsController.kt | 5 +- .../HomeLayoutSettingBottomDialogFragment.kt | 78 +++++++++++++++++++ .../recent/RecentRoomCarouselController.kt | 8 +- .../bottom_sheet_home_layout_settings.xml | 67 ++++++++++++++++ vector/src/main/res/menu/menu_new_home.xml | 6 +- vector/src/main/res/values/strings.xml | 9 +++ 12 files changed, 314 insertions(+), 37 deletions(-) create mode 100644 changelog.d/6506.wip create mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/home/HomeLayoutPreferencesStore.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/home/layout/HomeLayoutSettingBottomDialogFragment.kt create mode 100644 vector/src/main/res/layout/bottom_sheet_home_layout_settings.xml diff --git a/changelog.d/6506.wip b/changelog.d/6506.wip new file mode 100644 index 0000000000..344c0bca2f --- /dev/null +++ b/changelog.d/6506.wip @@ -0,0 +1 @@ +[App Layout] added dialog to configure app layout diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index fe57b9f735..553b45ad81 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -56,6 +56,7 @@ import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.disclaimer.showDisclaimerDialog +import im.vector.app.features.home.room.list.home.layout.HomeLayoutSettingBottomDialogFragment import im.vector.app.features.matrixto.MatrixToBottomSheet import im.vector.app.features.matrixto.OriginOfMatrixTo import im.vector.app.features.navigation.Navigator @@ -283,6 +284,11 @@ class HomeActivity : .show(supportFragmentManager, "SPACE_SETTINGS") } + private fun showLayoutSettings() { + HomeLayoutSettingBottomDialogFragment() + .show(supportFragmentManager, "LAYOUT_SETTINGS") + } + private fun openSpaceInvite(spaceId: String) { SpaceInviteBottomSheet.newInstance(spaceId) .show(supportFragmentManager, "SPACE_INVITE") @@ -596,6 +602,10 @@ class HomeActivity : navigator.openSettings(this) true } + R.id.menu_home_layout_settings -> { + showLayoutSettings() + true + } R.id.menu_home_invite_friends -> { launchInviteFriends() true diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeLayoutPreferencesStore.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeLayoutPreferencesStore.kt new file mode 100644 index 0000000000..11cd892406 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeLayoutPreferencesStore.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.list.home + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.preferencesDataStore +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import org.matrix.android.sdk.api.extensions.orFalse +import javax.inject.Inject + +private val Context.dataStore: DataStore by preferencesDataStore(name = "layout_preferences") + +class HomeLayoutPreferencesStore @Inject constructor( + private val context: Context +) { + + private val areRecentsEnbabled = booleanPreferencesKey("SETTINGS_PREFERENCES_HOME_RECENTS") + private val areFiltersEnabled = booleanPreferencesKey("SETTINGS_PREFERENCES_HOME_FILTERS") + private val isAZOrderingEnabled = booleanPreferencesKey("SETTINGS_PREFERENCES_USE_AZ_ORDER") + + val areRecentsEnabledFlow: Flow = context.dataStore.data + .map { preferences -> preferences[areRecentsEnbabled].orFalse() } + .distinctUntilChanged() + + val areFiltersEnabledFlow: Flow = context.dataStore.data + .map { preferences -> preferences[areFiltersEnabled].orFalse() } + .distinctUntilChanged() + + val isAZOrderingEnabledFlow: Flow = context.dataStore.data + .map { preferences -> preferences[isAZOrderingEnabled].orFalse() } + .distinctUntilChanged() + + suspend fun setRecentsEnabled(isEnabled: Boolean) { + context.dataStore.edit { settings -> + settings[areRecentsEnbabled] = isEnabled + } + } + + suspend fun setFiltersEnabled(isEnabled: Boolean) { + context.dataStore.edit { settings -> + settings[areFiltersEnabled] = isEnabled + } + } + + suspend fun setAZOrderingEnabled(isEnabled: Boolean) { + context.dataStore.edit { settings -> + settings[isAZOrderingEnabled] = isEnabled + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt index 1d9342b45b..9464033896 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt @@ -162,6 +162,15 @@ class HomeRoomListFragment @Inject constructor( }.launchIn(lifecycleScope) views.roomListView.adapter = concatAdapter + + // we need to force scroll when recents/filter tabs are added to make them visible + concatAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { + override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { + if (positionStart == 0) { + layoutManager.scrollToPosition(0) + } + } + }) } private fun setupFabs() { @@ -205,6 +214,9 @@ class HomeRoomListFragment @Inject constructor( } private fun setUpAdapters(sections: Set) { + concatAdapter.adapters.forEach { + concatAdapter.removeAdapter(it) + } sections.forEach { concatAdapter.addAdapter(getAdapterForData(it)) } @@ -234,12 +246,11 @@ class HomeRoomListFragment @Inject constructor( is HomeRoomSection.RoomSummaryData -> { HomeFilteredRoomsController( roomSummaryItemFactory, - showFilters = section.showFilters, ).also { controller -> controller.listener = this controller.onFilterChanged = ::onRoomFilterChanged section.filtersData.onEach { - controller.submitFiltersData(it) + controller.submitFiltersData(it.getOrNull()) }.launchIn(lifecycleScope) section.list.observe(viewLifecycleOwner) { list -> controller.submitList(list) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt index 1fed9eba86..711ba0c10a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt @@ -34,6 +34,7 @@ import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -53,12 +54,14 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.state.isPublic +import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.flow.flow class HomeRoomListViewModel @AssistedInject constructor( @Assisted initialState: HomeRoomListViewState, private val session: Session, private val spaceStateHandler: SpaceStateHandler, + private val preferencesStore: HomeLayoutPreferencesStore, ) : VectorViewModel(initialState) { @AssistedFactory @@ -82,17 +85,30 @@ class HomeRoomListViewModel @AssistedInject constructor( init { configureSections() + observePreferences() } - private fun configureSections() { + private fun observePreferences() { + preferencesStore.areRecentsEnabledFlow.onEach { + configureSections() + }.launchIn(viewModelScope) + + preferencesStore.isAZOrderingEnabledFlow.onEach { + configureSections() + }.launchIn(viewModelScope) + } + + private fun configureSections() = viewModelScope.launch { val newSections = mutableSetOf() - newSections.add(getRecentRoomsSection()) + val areSettingsEnabled = preferencesStore.areRecentsEnabledFlow.first() + + if (areSettingsEnabled) { + newSections.add(getRecentRoomsSection()) + } newSections.add(getFilteredRoomsSection()) - viewModelScope.launch { - _sections.emit(newSections) - } + _sections.emit(newSections) setState { copy(state = StateView.State.Content) @@ -111,13 +127,17 @@ class HomeRoomListViewModel @AssistedInject constructor( ) } - private fun getFilteredRoomsSection(): HomeRoomSection.RoomSummaryData { + private suspend fun getFilteredRoomsSection(): HomeRoomSection.RoomSummaryData { val builder = RoomSummaryQueryParams.Builder().also { it.memberships = listOf(Membership.JOIN) } val params = getFilteredQueryParams(HomeRoomFilter.ALL, builder.build()) - val sortOrder = RoomSortOrder.ACTIVITY // #6506 + val sortOrder = if (preferencesStore.isAZOrderingEnabledFlow.first()) { + RoomSortOrder.NAME + } else { + RoomSortOrder.ACTIVITY + } val liveResults = session.roomService().getFilteredPagedRoomSummariesLive( params, @@ -135,19 +155,18 @@ class HomeRoomListViewModel @AssistedInject constructor( .onEach { selectedSpaceOption -> val selectedSpace = selectedSpaceOption.orNull() liveResults.queryParams = liveResults.queryParams.copy( - spaceFilter = selectedSpace?.roomId.toActiveSpaceOrNoFilter() + spaceFilter = selectedSpace?.roomId.toActiveSpaceOrNoFilter() ) }.launchIn(viewModelScope) return HomeRoomSection.RoomSummaryData( list = liveResults.livePagedList, - showFilters = true, // #6506 filtersData = getFiltersDataFlow() ) } - private fun getFiltersDataFlow(): SharedFlow> { - val flow = MutableSharedFlow>(replay = 1) + private fun getFiltersDataFlow(): SharedFlow>> { + val flow = MutableSharedFlow>>(replay = 1) val favouritesFlow = session.flow() .liveRoomSummaries( @@ -168,25 +187,28 @@ class HomeRoomListViewModel @AssistedInject constructor( .map { it.isNotEmpty() } .distinctUntilChanged() - favouritesFlow.combine(dmsFLow) { hasFavourite, hasDm -> - hasFavourite to hasDm - }.onEach { (hasFavourite, hasDm) -> - val filtersData = mutableListOf( - HomeRoomFilter.ALL, - HomeRoomFilter.UNREADS - ) - if (hasFavourite) { - filtersData.add( - HomeRoomFilter.FAVOURITES + combine(favouritesFlow, dmsFLow, preferencesStore.areFiltersEnabledFlow) { hasFavourite, hasDm, areFiltersEnabled -> + Triple(hasFavourite, hasDm, areFiltersEnabled) + }.onEach { (hasFavourite, hasDm, areFiltersEnabled) -> + if (areFiltersEnabled) { + val filtersData = mutableListOf( + HomeRoomFilter.ALL, + HomeRoomFilter.UNREADS ) + if (hasFavourite) { + filtersData.add( + HomeRoomFilter.FAVOURITES + ) + } + if (hasDm) { + filtersData.add( + HomeRoomFilter.PEOPlE + ) + } + flow.emit(Optional.from(filtersData)) + } else { + flow.emit(Optional.empty()) } - if (hasDm) { - filtersData.add( - HomeRoomFilter.PEOPlE - ) - } - - flow.emit(filtersData) }.launchIn(viewModelScope) return flow diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt index f51b479d37..74ec46d6b7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt @@ -21,12 +21,12 @@ import androidx.paging.PagedList import im.vector.app.features.home.room.list.home.filter.HomeRoomFilter import kotlinx.coroutines.flow.SharedFlow import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.util.Optional sealed class HomeRoomSection { data class RoomSummaryData( val list: LiveData>, - val showFilters: Boolean, - val filtersData: SharedFlow> + val filtersData: SharedFlow>>, ) : HomeRoomSection() data class RecentRoomsData( diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt index 7c1f154d52..2d673bc089 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/filter/HomeFilteredRoomsController.kt @@ -28,7 +28,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary class HomeFilteredRoomsController( private val roomSummaryItemFactory: RoomSummaryItemFactory, - private val showFilters: Boolean, ) : PagedListEpoxyController( // Important it must match the PageList builder notify Looper modelBuildingHandler = createUIHandler() @@ -48,7 +47,7 @@ class HomeFilteredRoomsController( override fun addModels(models: List>) { val host = this - if (showFilters) { + if (host.filtersData != null) { roomFilterHeaderItem { id("filter_header") filtersData(host.filtersData) @@ -58,7 +57,7 @@ class HomeFilteredRoomsController( super.addModels(models) } - fun submitFiltersData(data: List) { + fun submitFiltersData(data: List?) { this.filtersData = data requestForcedModelBuild() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/layout/HomeLayoutSettingBottomDialogFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/layout/HomeLayoutSettingBottomDialogFragment.kt new file mode 100644 index 0000000000..0c4d64a1cc --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/layout/HomeLayoutSettingBottomDialogFragment.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.list.home.layout + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.lifecycleScope +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R +import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment +import im.vector.app.databinding.BottomSheetHomeLayoutSettingsBinding +import im.vector.app.features.home.room.list.home.HomeLayoutPreferencesStore +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import javax.inject.Inject + +@AndroidEntryPoint +class HomeLayoutSettingBottomDialogFragment : VectorBaseBottomSheetDialogFragment() { + + @Inject lateinit var preferencesStore: HomeLayoutPreferencesStore + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetHomeLayoutSettingsBinding { + return BottomSheetHomeLayoutSettingsBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + viewLifecycleOwner.lifecycleScope.launch { + views.homeLayoutSettingsRecents.isChecked = preferencesStore.areRecentsEnabledFlow.first() + views.homeLayoutSettingsFilters.isChecked = preferencesStore.areFiltersEnabledFlow.first() + + if (preferencesStore.isAZOrderingEnabledFlow.first()) { + views.homeLayoutSettingsSortName.isChecked = true + } else { + views.homeLayoutSettingsSortActivity.isChecked = true + } + } + + views.homeLayoutSettingsRecents.setOnCheckedChangeListener { _, isChecked -> + setRecentsEnabled(isChecked) + } + views.homeLayoutSettingsFilters.setOnCheckedChangeListener { _, isChecked -> + setFiltersEnabled(isChecked) + } + views.homeLayoutSettingsSortGroup.setOnCheckedChangeListener { _, checkedId -> + setAzOrderingEnabled(checkedId == R.id.home_layout_settings_sort_name) + } + } + + private fun setRecentsEnabled(isEnabled: Boolean) = lifecycleScope.launch { + preferencesStore.setRecentsEnabled(isEnabled) + } + + private fun setFiltersEnabled(isEnabled: Boolean) = lifecycleScope.launch { + preferencesStore.setFiltersEnabled(isEnabled) + } + + private fun setAzOrderingEnabled(isEnabled: Boolean) = lifecycleScope.launch { + preferencesStore.setAZOrderingEnabled(isEnabled) + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomCarouselController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomCarouselController.kt index 53832bbc74..ebec912779 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomCarouselController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomCarouselController.kt @@ -59,7 +59,13 @@ class RecentRoomCarouselController @Inject constructor( data?.let { data -> carousel { id("recents_carousel") - padding(Carousel.Padding(host.hPadding, host.itemSpacing)) + padding(Carousel.Padding( + host.hPadding, + 0, + host.hPadding, + 0, + host.itemSpacing) + ) withModelsFrom(data) { roomSummary -> val onClick = host.listener?.let { it::onRoomClicked } val onLongClick = host.listener?.let { it::onRoomLongClicked } diff --git a/vector/src/main/res/layout/bottom_sheet_home_layout_settings.xml b/vector/src/main/res/layout/bottom_sheet_home_layout_settings.xml new file mode 100644 index 0000000000..1766695354 --- /dev/null +++ b/vector/src/main/res/layout/bottom_sheet_home_layout_settings.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/menu/menu_new_home.xml b/vector/src/main/res/menu/menu_new_home.xml index e893624d54..6cd52e5608 100644 --- a/vector/src/main/res/menu/menu_new_home.xml +++ b/vector/src/main/res/menu/menu_new_home.xml @@ -3,6 +3,10 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> + + + app:showAsAction="ifRoom" /> diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 4502304cc7..0b62c16f92 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -427,6 +427,15 @@ Filter room names + Layout preferences + + + + Show filters + Show recents + Sort by + Activity + A - Z Invites