added dialog to change app layout settings (#6840)

This commit is contained in:
Nikita Fedrunov 2022-08-19 17:53:48 +02:00 committed by GitHub
parent 11b4ea5227
commit 0629cae183
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 314 additions and 37 deletions

1
changelog.d/6506.wip Normal file
View File

@ -0,0 +1 @@
[App Layout] added dialog to configure app layout

View File

@ -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

View File

@ -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<Preferences> 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<Boolean> = context.dataStore.data
.map { preferences -> preferences[areRecentsEnbabled].orFalse() }
.distinctUntilChanged()
val areFiltersEnabledFlow: Flow<Boolean> = context.dataStore.data
.map { preferences -> preferences[areFiltersEnabled].orFalse() }
.distinctUntilChanged()
val isAZOrderingEnabledFlow: Flow<Boolean> = 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
}
}
}

View File

@ -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<HomeRoomSection>) {
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)

View File

@ -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<HomeRoomListViewState, HomeRoomListAction, HomeRoomListViewEvents>(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<HomeRoomSection>()
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<List<HomeRoomFilter>> {
val flow = MutableSharedFlow<List<HomeRoomFilter>>(replay = 1)
private fun getFiltersDataFlow(): SharedFlow<Optional<List<HomeRoomFilter>>> {
val flow = MutableSharedFlow<Optional<List<HomeRoomFilter>>>(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

View File

@ -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<PagedList<RoomSummary>>,
val showFilters: Boolean,
val filtersData: SharedFlow<List<HomeRoomFilter>>
val filtersData: SharedFlow<Optional<List<HomeRoomFilter>>>,
) : HomeRoomSection()
data class RecentRoomsData(

View File

@ -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<RoomSummary>(
// Important it must match the PageList builder notify Looper
modelBuildingHandler = createUIHandler()
@ -48,7 +47,7 @@ class HomeFilteredRoomsController(
override fun addModels(models: List<EpoxyModel<*>>) {
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<HomeRoomFilter>) {
fun submitFiltersData(data: List<HomeRoomFilter>?) {
this.filtersData = data
requestForcedModelBuild()
}

View File

@ -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<BottomSheetHomeLayoutSettingsBinding>() {
@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)
}
}

View File

@ -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 }

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rootLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorSurface"
android:orientation="vertical">
<TextView
style="@style/Widget.Vector.TextView.Subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
android:text="@string/home_layout_preferences"
android:textAllCaps="true" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/home_layout_settings_recents"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_marginHorizontal="16dp"
android:checked="true"
android:text="@string/home_layout_preferences_recents" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/home_layout_settings_filters"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_marginHorizontal="16dp"
android:checked="true"
android:text="@string/home_layout_preferences_filters" />
<TextView
style="@style/Widget.Vector.TextView.Subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="8dp"
android:text="@string/home_layout_preferences_sort_by"
android:textAllCaps="true" />
<RadioGroup
android:id="@+id/home_layout_settings_sort_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:orientation="vertical">
<RadioButton
android:id="@+id/home_layout_settings_sort_activity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/home_layout_preferences_sort_activity"
android:textColor="?vctr_content_primary" />
<RadioButton
android:id="@+id/home_layout_settings_sort_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/home_layout_preferences_sort_name"
android:textColor="?vctr_content_primary" />
</RadioGroup>
</LinearLayout>

View File

@ -3,6 +3,10 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<item
android:id="@+id/menu_home_layout_settings"
android:title="@string/home_layout_preferences"/>
<item
android:id="@+id/menu_home_invite_friends"
android:title="@string/invite_friends"
@ -37,6 +41,6 @@
android:icon="@drawable/ic_home_search"
android:title="@string/home_filter_placeholder_home"
app:iconTint="?vctr_content_secondary"
app:showAsAction="always" />
app:showAsAction="ifRoom" />
</menu>

View File

@ -427,6 +427,15 @@
<!-- Home screen -->
<string name="home_filter_placeholder_home">Filter room names</string>
<string name="home_layout_preferences">Layout preferences</string>
<!-- Home screen layout settings -->
<string name="home_layout_preferences_filters">Show filters</string>
<string name="home_layout_preferences_recents">Show recents</string>
<string name="home_layout_preferences_sort_by">Sort by</string>
<string name="home_layout_preferences_sort_activity">Activity</string>
<string name="home_layout_preferences_sort_name">A - Z</string>
<!-- Home fragment -->
<string name="invitations_header">Invites</string>