diff --git a/changelog.d/6506.wip b/changelog.d/6506.wip new file mode 100644 index 00000000000..344c0bca2f4 --- /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 fe57b9f735c..553b45ad817 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/HomeLayoutPreferences.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeLayoutPreferences.kt new file mode 100644 index 00000000000..0d1e4a559ec --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeLayoutPreferences.kt @@ -0,0 +1,107 @@ +/* + * 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.SharedPreferences +import androidx.core.content.edit +import im.vector.app.core.di.DefaultPreferences +import javax.inject.Inject + +class HomeLayoutPreferences @Inject constructor( + @DefaultPreferences private val preferences: SharedPreferences +) { + + companion object { + const val SETTINGS_PREFERENCES_HOME_RECENTS = "SETTINGS_PREFERENCES_HOME_RECENTS" + const val SETTINGS_PREFERENCES_HOME_FILTERS = "SETTINGS_PREFERENCES_HOME_FILTERS" + const val SETTINGS_PREFERENCES_USE_AZ_ORDER = "SETTINGS_PREFERENCES_USE_AZ_ORDER" + } + + // We need to keep references, because it's kept as a Weak reference and so will be gathered by GC + private var filtersListener: SharedPreferences.OnSharedPreferenceChangeListener? = null + private var recentsListener: SharedPreferences.OnSharedPreferenceChangeListener? = null + private var orderListener: SharedPreferences.OnSharedPreferenceChangeListener? = null + + fun areRecentsEnabled(): Boolean { + return preferences.getBoolean(SETTINGS_PREFERENCES_HOME_RECENTS, false) + } + + fun setRecentsEnabled(isEnabled: Boolean) { + preferences.edit { + putBoolean(SETTINGS_PREFERENCES_HOME_RECENTS, isEnabled) + } + } + + fun areFiltersEnabled(): Boolean { + return preferences.getBoolean(SETTINGS_PREFERENCES_HOME_FILTERS, false) + } + + fun setFiltersEnabled(isEnabled: Boolean) { + preferences.edit { + putBoolean(SETTINGS_PREFERENCES_HOME_FILTERS, isEnabled) + } + } + + fun isAZOrderingEnabled(): Boolean { + return preferences.getBoolean(SETTINGS_PREFERENCES_USE_AZ_ORDER, false) + } + + fun setAZOrderingEnabled(isEnabled: Boolean) { + preferences.edit { + putBoolean(SETTINGS_PREFERENCES_USE_AZ_ORDER, isEnabled) + } + } + + fun registerFiltersListener(callBack: (Boolean) -> Unit) { + filtersListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key -> + when (key) { + SETTINGS_PREFERENCES_HOME_FILTERS -> { + callBack.invoke(areFiltersEnabled()) + } + } + } + preferences.registerOnSharedPreferenceChangeListener(filtersListener) + } + + fun registerRecentsListener(callBack: (Boolean) -> Unit) { + recentsListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key -> + when (key) { + SETTINGS_PREFERENCES_HOME_RECENTS -> { + callBack.invoke(areRecentsEnabled()) + } + } + } + preferences.registerOnSharedPreferenceChangeListener(recentsListener) + } + + fun registerOrderingListener(callBack: (Boolean) -> Unit) { + orderListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key -> + when (key) { + SETTINGS_PREFERENCES_USE_AZ_ORDER -> { + callBack.invoke(isAZOrderingEnabled()) + } + } + } + preferences.registerOnSharedPreferenceChangeListener(orderListener) + } + + fun unregisterListeners() { + preferences.unregisterOnSharedPreferenceChangeListener(filtersListener) + preferences.unregisterOnSharedPreferenceChangeListener(recentsListener) + preferences.unregisterOnSharedPreferenceChangeListener(orderListener) + } +} 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 1d9342b45b4..94640338962 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 1fed9eba86e..dce056b19f6 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 @@ -53,12 +53,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 preferences: HomeLayoutPreferences, ) : VectorViewModel(initialState) { @AssistedFactory @@ -78,16 +80,45 @@ class HomeRoomListViewModel @AssistedInject constructor( private val _sections = MutableSharedFlow>(replay = 1) val sections = _sections.asSharedFlow() + private val filtersPreferencesFlow = MutableSharedFlow(replay = 1) + private var filteredPagedRoomSummariesLive: UpdatableLivePageResult? = null init { configureSections() + observePreferences() + } + + private fun observePreferences() { + preferences.registerFiltersListener { areFiltersEnabled -> + viewModelScope.launch { + filtersPreferencesFlow.emit(areFiltersEnabled) + } + } + viewModelScope.launch { + filtersPreferencesFlow.emit(preferences.areFiltersEnabled()) + } + + preferences.registerRecentsListener { _ -> + configureSections() + } + + preferences.registerOrderingListener { _ -> + configureSections() + } + } + + override fun onCleared() { + preferences.unregisterListeners() + super.onCleared() } private fun configureSections() { val newSections = mutableSetOf() - newSections.add(getRecentRoomsSection()) + if (preferences.areRecentsEnabled()) { + newSections.add(getRecentRoomsSection()) + } newSections.add(getFilteredRoomsSection()) viewModelScope.launch { @@ -117,7 +148,11 @@ class HomeRoomListViewModel @AssistedInject constructor( } val params = getFilteredQueryParams(HomeRoomFilter.ALL, builder.build()) - val sortOrder = RoomSortOrder.ACTIVITY // #6506 + val sortOrder = if (preferences.isAZOrderingEnabled()) { + RoomSortOrder.NAME + } else { + RoomSortOrder.ACTIVITY + } val liveResults = session.roomService().getFilteredPagedRoomSummariesLive( params, @@ -141,13 +176,12 @@ class HomeRoomListViewModel @AssistedInject constructor( 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,26 +202,32 @@ 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 - ) - } - if (hasDm) { - filtersData.add( - HomeRoomFilter.PEOPlE - ) - } - - flow.emit(filtersData) - }.launchIn(viewModelScope) + favouritesFlow + .combine(dmsFLow) { hasFavourite, hasDm -> + hasFavourite to hasDm + }.combine(filtersPreferencesFlow) { (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()) + } + }.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 f51b479d37b..10c4a7a8d41 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 7c1f154d52e..96b976fe332 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 @@ -27,8 +27,7 @@ import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.RoomSummary class HomeFilteredRoomsController( - private val roomSummaryItemFactory: RoomSummaryItemFactory, - private val showFilters: Boolean, + private val roomSummaryItemFactory: RoomSummaryItemFactory ) : 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 00000000000..0d14f02f5dd --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/layout/HomeLayoutSettingBottomDialogFragment.kt @@ -0,0 +1,67 @@ +/* + * 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 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.HomeLayoutPreferences +import javax.inject.Inject + +@AndroidEntryPoint +class HomeLayoutSettingBottomDialogFragment : VectorBaseBottomSheetDialogFragment() { + + @Inject lateinit var preferences: HomeLayoutPreferences + + 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) + + views.homeLayoutSettingsRecents.isChecked = preferences.areRecentsEnabled() + views.homeLayoutSettingsFilters.isChecked = preferences.areFiltersEnabled() + + if (preferences.isAZOrderingEnabled()) { + views.homeLayoutSettingsSortName.isChecked = true + } else { + views.homeLayoutSettingsSortActivity.isChecked = true + } + + views.homeLayoutSettingsRecents.setOnCheckedChangeListener { _, isChecked -> + preferences.setRecentsEnabled(isChecked) + } + + views.homeLayoutSettingsFilters.setOnCheckedChangeListener { _, isChecked -> + preferences.setFiltersEnabled(isChecked) + } + + views.homeLayoutSettingsSortGroup.setOnCheckedChangeListener { _, checkedId -> + preferences.setAZOrderingEnabled(checkedId == R.id.home_layout_settings_sort_name) + } + + views.homeLayoutSettingsDone.setOnClickListener { + dismiss() + } + } +} 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 53832bbc742..ebec9127793 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 00000000000..0c87a03cb0a --- /dev/null +++ b/vector/src/main/res/layout/bottom_sheet_home_layout_settings.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + +