ganfra
7 months ago
committed by
GitHub
45 changed files with 765 additions and 37 deletions
@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
|
||||
/* |
||||
* Copyright (c) 2024 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 io.element.android.features.roomlist.impl.filters |
||||
|
||||
import io.element.android.features.roomlist.impl.R |
||||
|
||||
/** |
||||
* Enum class representing the different filters that can be applied to the room list. |
||||
* Order is important. |
||||
*/ |
||||
enum class RoomListFilter(val stringResource: Int) { |
||||
Rooms(R.string.screen_roomlist_filter_rooms), |
||||
People(R.string.screen_roomlist_filter_people), |
||||
Unread(R.string.screen_roomlist_filter_unreads), |
||||
Favourites(R.string.screen_roomlist_filter_favourites); |
||||
|
||||
val oppositeFilter: RoomListFilter? |
||||
get() = when (this) { |
||||
Rooms -> People |
||||
People -> Rooms |
||||
Unread -> null |
||||
Favourites -> null |
||||
} |
||||
} |
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
/* |
||||
* Copyright (c) 2024 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 io.element.android.features.roomlist.impl.filters |
||||
|
||||
sealed interface RoomListFiltersEvents { |
||||
data object ClearSelectedFilters : RoomListFiltersEvents |
||||
data class ToggleFilter(val filter: RoomListFilter) : RoomListFiltersEvents |
||||
} |
@ -0,0 +1,98 @@
@@ -0,0 +1,98 @@
|
||||
/* |
||||
* Copyright (c) 2024 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 io.element.android.features.roomlist.impl.filters |
||||
|
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.runtime.LaunchedEffect |
||||
import androidx.compose.runtime.collectAsState |
||||
import androidx.compose.runtime.getValue |
||||
import androidx.compose.runtime.mutableStateOf |
||||
import androidx.compose.runtime.saveable.rememberSaveable |
||||
import androidx.compose.runtime.setValue |
||||
import io.element.android.libraries.architecture.Presenter |
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService |
||||
import io.element.android.libraries.featureflag.api.FeatureFlags |
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService |
||||
import kotlinx.collections.immutable.toPersistentList |
||||
import javax.inject.Inject |
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter as MatrixRoomListFilter |
||||
|
||||
class RoomListFiltersPresenter @Inject constructor( |
||||
private val roomListService: RoomListService, |
||||
private val featureFlagService: FeatureFlagService, |
||||
) : Presenter<RoomListFiltersState> { |
||||
@Composable |
||||
override fun present(): RoomListFiltersState { |
||||
val isFeatureEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.RoomListFilters).collectAsState(false) |
||||
var unselectedFilters: Set<RoomListFilter> by rememberSaveable { |
||||
mutableStateOf(RoomListFilter.entries.toSet()) |
||||
} |
||||
var selectedFilters: Set<RoomListFilter> by rememberSaveable { |
||||
mutableStateOf(emptySet()) |
||||
} |
||||
|
||||
fun updateFilters(newSelectedFilters: Set<RoomListFilter>) { |
||||
selectedFilters = newSelectedFilters |
||||
unselectedFilters = RoomListFilter.entries.toSet() - |
||||
selectedFilters - |
||||
selectedFilters.mapNotNull { it.oppositeFilter }.toSet() |
||||
} |
||||
|
||||
fun handleEvents(event: RoomListFiltersEvents) { |
||||
when (event) { |
||||
is RoomListFiltersEvents.ToggleFilter -> { |
||||
val newSelectedFilters = if (selectedFilters.contains(event.filter)) { |
||||
selectedFilters - event.filter |
||||
} else { |
||||
selectedFilters + event.filter |
||||
} |
||||
updateFilters(newSelectedFilters) |
||||
} |
||||
RoomListFiltersEvents.ClearSelectedFilters -> { |
||||
updateFilters(newSelectedFilters = emptySet()) |
||||
} |
||||
} |
||||
} |
||||
|
||||
LaunchedEffect(isFeatureEnabled) { |
||||
if (!isFeatureEnabled) { |
||||
updateFilters(emptySet()) |
||||
} |
||||
} |
||||
|
||||
LaunchedEffect(selectedFilters) { |
||||
val allRoomsFilter = MatrixRoomListFilter.All( |
||||
selectedFilters.map { roomListFilter -> |
||||
when (roomListFilter) { |
||||
RoomListFilter.Rooms -> MatrixRoomListFilter.Category.Group |
||||
RoomListFilter.People -> MatrixRoomListFilter.Category.People |
||||
RoomListFilter.Unread -> MatrixRoomListFilter.Unread |
||||
RoomListFilter.Favourites -> MatrixRoomListFilter.Favorite |
||||
} |
||||
}.plus(MatrixRoomListFilter.NonLeft) |
||||
) |
||||
roomListService.allRooms.updateFilter(allRoomsFilter) |
||||
} |
||||
|
||||
return RoomListFiltersState( |
||||
unselectedFilters = unselectedFilters.toPersistentList(), |
||||
selectedFilters = selectedFilters.toPersistentList(), |
||||
isFeatureEnabled = isFeatureEnabled, |
||||
eventSink = ::handleEvents |
||||
) |
||||
} |
||||
} |
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
/* |
||||
* Copyright (c) 2024 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 io.element.android.features.roomlist.impl.filters |
||||
|
||||
import kotlinx.collections.immutable.ImmutableList |
||||
|
||||
data class RoomListFiltersState( |
||||
val unselectedFilters: ImmutableList<RoomListFilter>, |
||||
val selectedFilters: ImmutableList<RoomListFilter>, |
||||
val isFeatureEnabled: Boolean, |
||||
val eventSink: (RoomListFiltersEvents) -> Unit, |
||||
) { |
||||
val hasAnyFilterSelected = selectedFilters.isNotEmpty() |
||||
} |
@ -0,0 +1,45 @@
@@ -0,0 +1,45 @@
|
||||
/* |
||||
* Copyright (c) 2024 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 io.element.android.features.roomlist.impl.filters |
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider |
||||
import kotlinx.collections.immutable.ImmutableList |
||||
import kotlinx.collections.immutable.persistentListOf |
||||
import kotlinx.collections.immutable.toImmutableList |
||||
|
||||
class RoomListFiltersStateProvider : PreviewParameterProvider<RoomListFiltersState> { |
||||
override val values: Sequence<RoomListFiltersState> |
||||
get() = sequenceOf( |
||||
aRoomListFiltersState(), |
||||
aRoomListFiltersState( |
||||
selectedFilters = persistentListOf(RoomListFilter.Rooms, RoomListFilter.Favourites), |
||||
unselectedFilters = persistentListOf(RoomListFilter.Unread), |
||||
), |
||||
) |
||||
} |
||||
|
||||
fun aRoomListFiltersState( |
||||
unselectedFilters: ImmutableList<RoomListFilter> = RoomListFilter.entries.toImmutableList(), |
||||
selectedFilters: ImmutableList<RoomListFilter> = persistentListOf(), |
||||
isFeatureEnabled: Boolean = true, |
||||
eventSink: (RoomListFiltersEvents) -> Unit = {}, |
||||
) = RoomListFiltersState( |
||||
unselectedFilters = unselectedFilters, |
||||
selectedFilters = selectedFilters, |
||||
isFeatureEnabled = isFeatureEnabled, |
||||
eventSink = eventSink, |
||||
) |
@ -0,0 +1,173 @@
@@ -0,0 +1,173 @@
|
||||
/* |
||||
* Copyright (c) 2024 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 io.element.android.features.roomlist.impl.filters |
||||
|
||||
import androidx.compose.animation.AnimatedVisibility |
||||
import androidx.compose.foundation.ExperimentalFoundationApi |
||||
import androidx.compose.foundation.background |
||||
import androidx.compose.foundation.layout.Arrangement |
||||
import androidx.compose.foundation.layout.Box |
||||
import androidx.compose.foundation.layout.Row |
||||
import androidx.compose.foundation.layout.fillMaxWidth |
||||
import androidx.compose.foundation.layout.height |
||||
import androidx.compose.foundation.layout.padding |
||||
import androidx.compose.foundation.lazy.LazyListScope |
||||
import androidx.compose.foundation.lazy.LazyRow |
||||
import androidx.compose.foundation.lazy.items |
||||
import androidx.compose.foundation.lazy.rememberLazyListState |
||||
import androidx.compose.foundation.shape.CircleShape |
||||
import androidx.compose.material3.FilterChip |
||||
import androidx.compose.material3.FilterChipDefaults |
||||
import androidx.compose.material3.minimumInteractiveComponentSize |
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.ui.Alignment |
||||
import androidx.compose.ui.Modifier |
||||
import androidx.compose.ui.draw.clip |
||||
import androidx.compose.ui.res.stringResource |
||||
import androidx.compose.ui.tooling.preview.PreviewParameter |
||||
import androidx.compose.ui.unit.dp |
||||
import io.element.android.compound.theme.ElementTheme |
||||
import io.element.android.compound.tokens.generated.CompoundIcons |
||||
import io.element.android.libraries.designsystem.modifiers.fadingEdge |
||||
import io.element.android.libraries.designsystem.modifiers.horizontalFadingEdgesBrush |
||||
import io.element.android.libraries.designsystem.preview.ElementPreview |
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight |
||||
import io.element.android.libraries.designsystem.theme.components.Icon |
||||
import io.element.android.libraries.designsystem.theme.components.IconButton |
||||
import io.element.android.libraries.designsystem.theme.components.Text |
||||
import io.element.android.libraries.testtags.TestTags |
||||
import io.element.android.libraries.testtags.testTag |
||||
import kotlinx.collections.immutable.ImmutableList |
||||
|
||||
@Composable |
||||
fun RoomListFiltersView( |
||||
state: RoomListFiltersState, |
||||
modifier: Modifier = Modifier |
||||
) { |
||||
fun onClearFiltersClicked() { |
||||
state.eventSink(RoomListFiltersEvents.ClearSelectedFilters) |
||||
} |
||||
|
||||
fun onFilterClicked(filter: RoomListFilter) { |
||||
state.eventSink(RoomListFiltersEvents.ToggleFilter(filter)) |
||||
} |
||||
|
||||
val startPadding = if (state.hasAnyFilterSelected) 4.dp else 16.dp |
||||
Row( |
||||
modifier = modifier.padding(start = startPadding, end = 16.dp), |
||||
verticalAlignment = Alignment.CenterVertically, |
||||
) { |
||||
AnimatedVisibility(visible = state.hasAnyFilterSelected) { |
||||
RoomListClearFiltersButton( |
||||
modifier = Modifier.testTag(TestTags.homeScreenClearFilters), |
||||
onClick = ::onClearFiltersClicked |
||||
) |
||||
} |
||||
val lazyListState = rememberLazyListState() |
||||
val fadingEdgesBrush = horizontalFadingEdgesBrush( |
||||
showLeft = lazyListState.canScrollBackward, |
||||
showRight = lazyListState.canScrollForward |
||||
) |
||||
LazyRow( |
||||
modifier = Modifier |
||||
.fillMaxWidth() |
||||
.fadingEdge(fadingEdgesBrush), |
||||
state = lazyListState, |
||||
horizontalArrangement = Arrangement.spacedBy(8.dp) |
||||
) { |
||||
roomListFilters(state.selectedFilters, selected = true, onClick = ::onFilterClicked) |
||||
roomListFilters(state.unselectedFilters, selected = false, onClick = ::onFilterClicked) |
||||
} |
||||
} |
||||
} |
||||
|
||||
@OptIn(ExperimentalFoundationApi::class) |
||||
private fun LazyListScope.roomListFilters( |
||||
filters: ImmutableList<RoomListFilter>, |
||||
selected: Boolean, |
||||
onClick: (RoomListFilter) -> Unit, |
||||
) { |
||||
items( |
||||
items = filters, |
||||
key = { it.ordinal }, |
||||
) { filter -> |
||||
RoomListFilterView( |
||||
modifier = Modifier.animateItemPlacement(), |
||||
roomListFilter = filter, |
||||
selected = selected, |
||||
onClick = onClick, |
||||
) |
||||
} |
||||
} |
||||
|
||||
@Composable |
||||
private fun RoomListClearFiltersButton( |
||||
onClick: () -> Unit, |
||||
modifier: Modifier = Modifier |
||||
) { |
||||
IconButton( |
||||
modifier = modifier, |
||||
onClick = onClick, |
||||
) { |
||||
Box( |
||||
modifier = Modifier |
||||
.clip(CircleShape) |
||||
.background(ElementTheme.colors.bgActionPrimaryRest) |
||||
) { |
||||
Icon( |
||||
modifier = Modifier.align(Alignment.Center), |
||||
imageVector = CompoundIcons.Close(), |
||||
tint = ElementTheme.colors.iconOnSolidPrimary, |
||||
contentDescription = stringResource(id = io.element.android.libraries.ui.strings.R.string.action_clear), |
||||
) |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Composable |
||||
private fun RoomListFilterView( |
||||
roomListFilter: RoomListFilter, |
||||
selected: Boolean, |
||||
onClick: (RoomListFilter) -> Unit, |
||||
modifier: Modifier = Modifier |
||||
) { |
||||
FilterChip( |
||||
selected = selected, |
||||
onClick = { onClick(roomListFilter) }, |
||||
modifier = modifier |
||||
.minimumInteractiveComponentSize() |
||||
.height(36.dp), |
||||
shape = CircleShape, |
||||
colors = FilterChipDefaults.filterChipColors( |
||||
containerColor = ElementTheme.colors.bgCanvasDefault, |
||||
selectedContainerColor = ElementTheme.colors.bgActionPrimaryRest, |
||||
labelColor = ElementTheme.colors.textPrimary, |
||||
selectedLabelColor = ElementTheme.colors.textOnSolidPrimary, |
||||
), |
||||
label = { |
||||
Text(text = stringResource(id = roomListFilter.stringResource)) |
||||
} |
||||
) |
||||
} |
||||
|
||||
@PreviewsDayNight |
||||
@Composable |
||||
internal fun RoomListFiltersViewPreview(@PreviewParameter(RoomListFiltersStateProvider::class) state: RoomListFiltersState) = ElementPreview { |
||||
RoomListFiltersView( |
||||
state = state, |
||||
) |
||||
} |
@ -0,0 +1,125 @@
@@ -0,0 +1,125 @@
|
||||
/* |
||||
* Copyright (c) 2024 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 io.element.android.features.roomlist.impl.filters |
||||
|
||||
import app.cash.molecule.RecompositionMode |
||||
import app.cash.molecule.moleculeFlow |
||||
import app.cash.turbine.test |
||||
import com.google.common.truth.Truth.assertThat |
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService |
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService |
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService |
||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService |
||||
import io.element.android.tests.testutils.awaitLastSequentialItem |
||||
import kotlinx.coroutines.test.runTest |
||||
import org.junit.Test |
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter as MatrixRoomListFilter |
||||
|
||||
class RoomListFiltersPresenterTests { |
||||
@Test |
||||
fun `present - initial state`() = runTest { |
||||
val presenter = createRoomListFiltersPresenter() |
||||
moleculeFlow(RecompositionMode.Immediate) { |
||||
presenter.present() |
||||
}.test { |
||||
awaitItem().let { state -> |
||||
assertThat(state.selectedFilters).isEmpty() |
||||
assertThat(state.hasAnyFilterSelected).isFalse() |
||||
assertThat(state.unselectedFilters).containsExactly( |
||||
RoomListFilter.Rooms, |
||||
RoomListFilter.People, |
||||
RoomListFilter.Unread, |
||||
RoomListFilter.Favourites, |
||||
) |
||||
} |
||||
cancelAndIgnoreRemainingEvents() |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `present - toggle rooms filter`() = runTest { |
||||
val roomListService = FakeRoomListService() |
||||
val presenter = createRoomListFiltersPresenter(roomListService) |
||||
moleculeFlow(RecompositionMode.Immediate) { |
||||
presenter.present() |
||||
}.test { |
||||
awaitItem().eventSink.invoke(RoomListFiltersEvents.ToggleFilter(RoomListFilter.Rooms)) |
||||
|
||||
awaitLastSequentialItem().let { state -> |
||||
|
||||
assertThat(state.selectedFilters).containsExactly(RoomListFilter.Rooms) |
||||
assertThat(state.hasAnyFilterSelected).isTrue() |
||||
assertThat(state.unselectedFilters).containsExactly( |
||||
RoomListFilter.Unread, |
||||
RoomListFilter.Favourites, |
||||
) |
||||
val roomListCurrentFilter = roomListService.allRooms.currentFilter.value as MatrixRoomListFilter.All |
||||
assertThat(roomListCurrentFilter.filters).containsExactly( |
||||
MatrixRoomListFilter.NonLeft, |
||||
MatrixRoomListFilter.Category.Group, |
||||
) |
||||
|
||||
state.eventSink.invoke(RoomListFiltersEvents.ToggleFilter(RoomListFilter.Rooms)) |
||||
} |
||||
|
||||
awaitLastSequentialItem().let { state -> |
||||
assertThat(state.selectedFilters).isEmpty() |
||||
assertThat(state.hasAnyFilterSelected).isFalse() |
||||
assertThat(state.unselectedFilters).containsExactly( |
||||
RoomListFilter.Rooms, |
||||
RoomListFilter.People, |
||||
RoomListFilter.Unread, |
||||
RoomListFilter.Favourites, |
||||
) |
||||
val roomListCurrentFilter = roomListService.allRooms.currentFilter.value as MatrixRoomListFilter.All |
||||
assertThat(roomListCurrentFilter.filters).containsExactly( |
||||
MatrixRoomListFilter.NonLeft, |
||||
) |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `present - clear filters event`() = runTest { |
||||
val roomListService = FakeRoomListService() |
||||
val presenter = createRoomListFiltersPresenter(roomListService) |
||||
moleculeFlow(RecompositionMode.Immediate) { |
||||
presenter.present() |
||||
}.test { |
||||
awaitItem().eventSink.invoke(RoomListFiltersEvents.ToggleFilter(RoomListFilter.Rooms)) |
||||
awaitLastSequentialItem().let { state -> |
||||
assertThat(state.selectedFilters).isNotEmpty() |
||||
assertThat(state.hasAnyFilterSelected).isTrue() |
||||
state.eventSink.invoke(RoomListFiltersEvents.ClearSelectedFilters) |
||||
} |
||||
awaitLastSequentialItem().let { state -> |
||||
assertThat(state.selectedFilters).isEmpty() |
||||
assertThat(state.hasAnyFilterSelected).isFalse() |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
fun createRoomListFiltersPresenter( |
||||
roomListService: RoomListService = FakeRoomListService(), |
||||
featureFlagService: FeatureFlagService = FakeFeatureFlagService(), |
||||
): RoomListFiltersPresenter { |
||||
return RoomListFiltersPresenter( |
||||
roomListService = roomListService, |
||||
featureFlagService = featureFlagService, |
||||
) |
||||
} |
@ -0,0 +1,72 @@
@@ -0,0 +1,72 @@
|
||||
/* |
||||
* Copyright (c) 2024 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 io.element.android.features.roomlist.impl.filters |
||||
|
||||
import androidx.activity.ComponentActivity |
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule |
||||
import androidx.test.ext.junit.runners.AndroidJUnit4 |
||||
import io.element.android.features.roomlist.impl.R |
||||
import io.element.android.libraries.testtags.TestTags |
||||
import io.element.android.tests.testutils.EventsRecorder |
||||
import io.element.android.tests.testutils.clickOn |
||||
import io.element.android.tests.testutils.pressTag |
||||
import kotlinx.collections.immutable.persistentListOf |
||||
import kotlinx.collections.immutable.toImmutableList |
||||
import org.junit.Rule |
||||
import org.junit.Test |
||||
import org.junit.runner.RunWith |
||||
|
||||
@RunWith(AndroidJUnit4::class) |
||||
class RoomListFiltersViewTests { |
||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>() |
||||
|
||||
@Test |
||||
fun `clicking on filters generates expected Event`() { |
||||
val eventsRecorder = EventsRecorder<RoomListFiltersEvents>() |
||||
rule.setContent { |
||||
RoomListFiltersView( |
||||
state = aRoomListFiltersState(eventSink = eventsRecorder), |
||||
) |
||||
} |
||||
rule.clickOn(R.string.screen_roomlist_filter_rooms) |
||||
eventsRecorder.assertList( |
||||
listOf( |
||||
RoomListFiltersEvents.ToggleFilter(RoomListFilter.Rooms), |
||||
) |
||||
) |
||||
} |
||||
|
||||
@Test |
||||
fun `clicking on clear filters generates expected Event`() { |
||||
val eventsRecorder = EventsRecorder<RoomListFiltersEvents>() |
||||
rule.setContent { |
||||
RoomListFiltersView( |
||||
state = aRoomListFiltersState( |
||||
unselectedFilters = persistentListOf(), |
||||
selectedFilters = RoomListFilter.entries.toImmutableList(), |
||||
eventSink = eventsRecorder |
||||
), |
||||
) |
||||
} |
||||
rule.pressTag(TestTags.homeScreenClearFilters.value) |
||||
eventsRecorder.assertList( |
||||
listOf( |
||||
RoomListFiltersEvents.ClearSelectedFilters, |
||||
) |
||||
) |
||||
} |
||||
} |
@ -0,0 +1,57 @@
@@ -0,0 +1,57 @@
|
||||
/* |
||||
* Copyright (c) 2024 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 io.element.android.libraries.designsystem.modifiers |
||||
|
||||
import androidx.compose.animation.animateColorAsState |
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.runtime.getValue |
||||
import androidx.compose.ui.Modifier |
||||
import androidx.compose.ui.draw.drawWithContent |
||||
import androidx.compose.ui.graphics.BlendMode |
||||
import androidx.compose.ui.graphics.Brush |
||||
import androidx.compose.ui.graphics.Color |
||||
import androidx.compose.ui.graphics.CompositingStrategy |
||||
import androidx.compose.ui.graphics.graphicsLayer |
||||
|
||||
@Composable |
||||
fun horizontalFadingEdgesBrush( |
||||
showLeft: Boolean, |
||||
showRight: Boolean, |
||||
percent: Float = 0.1f, |
||||
): Brush { |
||||
val leftColor by animateColorAsState( |
||||
targetValue = if (showLeft) Color.Transparent else Color.White, |
||||
label = "AnimateLeftColor", |
||||
) |
||||
val rightColor by animateColorAsState( |
||||
targetValue = if (showRight) Color.Transparent else Color.White, |
||||
label = "AnimateRightColor", |
||||
) |
||||
return Brush.horizontalGradient( |
||||
0f to leftColor, |
||||
percent to Color.White, |
||||
1f - percent to Color.White, |
||||
1f to rightColor |
||||
) |
||||
} |
||||
|
||||
fun Modifier.fadingEdge(brush: Brush) = this |
||||
.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen) |
||||
.drawWithContent { |
||||
drawContent() |
||||
drawRect(brush = brush, blendMode = BlendMode.DstIn) |
||||
} |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
0
tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-11_12_null_0,NEXUS_5,1.0,en].png → tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-12_13_null_0,NEXUS_5,1.0,en].png
0
tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-11_12_null_0,NEXUS_5,1.0,en].png → tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-12_13_null_0,NEXUS_5,1.0,en].png
0
tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-11_12_null_1,NEXUS_5,1.0,en].png → tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-12_13_null_1,NEXUS_5,1.0,en].png
0
tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-11_12_null_1,NEXUS_5,1.0,en].png → tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-12_13_null_1,NEXUS_5,1.0,en].png
0
tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-11_13_null_0,NEXUS_5,1.0,en].png → tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-12_14_null_0,NEXUS_5,1.0,en].png
0
tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-11_13_null_0,NEXUS_5,1.0,en].png → tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-12_14_null_0,NEXUS_5,1.0,en].png
0
tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-11_13_null_1,NEXUS_5,1.0,en].png → tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-12_14_null_1,NEXUS_5,1.0,en].png
0
tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-11_13_null_1,NEXUS_5,1.0,en].png → tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-12_14_null_1,NEXUS_5,1.0,en].png
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in new issue