diff --git a/.maestro/tests/init.yaml b/.maestro/tests/init.yaml index 656c654fcb..acd5f86dfd 100644 --- a/.maestro/tests/init.yaml +++ b/.maestro/tests/init.yaml @@ -3,6 +3,5 @@ appId: ${APP_ID} - clearState - launchApp: clearKeychain: true -- tapOn: "Close showkase button" - runFlow: ./assertions/assertInitDisplayed.yaml - takeScreenshot: build/maestro/000-FirstScreen diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt index 519c4e734b..32f45bd77d 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -16,7 +16,6 @@ package io.element.android.appnav -import android.app.Activity import android.content.Intent import android.os.Parcelable import androidx.compose.foundation.layout.Box @@ -24,7 +23,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.lifecycleScope import com.bumble.appyx.core.composable.Children import com.bumble.appyx.core.modality.BuildContext @@ -53,7 +51,6 @@ import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.UserId -import io.element.android.tests.uitests.openShowkase import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -140,17 +137,11 @@ class RootFlowNode @AssistedInject constructor( @Composable override fun View(modifier: Modifier) { - val activity = LocalContext.current as Activity - fun openShowkase() { - openShowkase(activity) - } - val state = presenter.present() RootView( state = state, modifier = modifier, onOpenBugReport = this::onOpenBugReport, - onOpenShowkase = ::openShowkase ) { Children( navModel = backstack, diff --git a/appnav/src/main/kotlin/io/element/android/appnav/root/RootEvents.kt b/appnav/src/main/kotlin/io/element/android/appnav/root/RootEvents.kt deleted file mode 100644 index f6e4103017..0000000000 --- a/appnav/src/main/kotlin/io/element/android/appnav/root/RootEvents.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) 2023 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.appnav.root - -sealed interface RootEvents { - object HideShowkaseButton : RootEvents -} diff --git a/appnav/src/main/kotlin/io/element/android/appnav/root/RootPresenter.kt b/appnav/src/main/kotlin/io/element/android/appnav/root/RootPresenter.kt index a3b73bc4d9..9ac4d34b98 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/root/RootPresenter.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/root/RootPresenter.kt @@ -17,8 +17,6 @@ package io.element.android.appnav.root import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable import io.element.android.features.rageshake.api.crash.CrashDetectionPresenter import io.element.android.features.rageshake.api.detection.RageshakeDetectionPresenter import io.element.android.libraries.architecture.Presenter @@ -31,23 +29,12 @@ class RootPresenter @Inject constructor( @Composable override fun present(): RootState { - val isShowkaseButtonVisible = rememberSaveable { - mutableStateOf(true) - } val rageshakeDetectionState = rageshakeDetectionPresenter.present() val crashDetectionState = crashDetectionPresenter.present() - fun handleEvent(event: RootEvents) { - when (event) { - RootEvents.HideShowkaseButton -> isShowkaseButtonVisible.value = false - } - } - return RootState( - isShowkaseButtonVisible = isShowkaseButtonVisible.value, rageshakeDetectionState = rageshakeDetectionState, crashDetectionState = crashDetectionState, - eventSink = ::handleEvent ) } } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/root/RootState.kt b/appnav/src/main/kotlin/io/element/android/appnav/root/RootState.kt index ab3be17746..8389e1f144 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/root/RootState.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/root/RootState.kt @@ -22,8 +22,6 @@ import io.element.android.features.rageshake.api.detection.RageshakeDetectionSta @Immutable data class RootState( - val isShowkaseButtonVisible: Boolean, val rageshakeDetectionState: RageshakeDetectionState, val crashDetectionState: CrashDetectionState, - val eventSink: (RootEvents) -> Unit ) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/root/RootStateProvider.kt b/appnav/src/main/kotlin/io/element/android/appnav/root/RootStateProvider.kt index 9ed9d365f2..645fbccd0d 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/root/RootStateProvider.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/root/RootStateProvider.kt @@ -24,12 +24,10 @@ open class RootStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aRootState().copy( - isShowkaseButtonVisible = true, rageshakeDetectionState = aRageshakeDetectionState().copy(showDialog = false), crashDetectionState = aCrashDetectionState().copy(crashDetected = true), ), aRootState().copy( - isShowkaseButtonVisible = true, rageshakeDetectionState = aRageshakeDetectionState().copy(showDialog = true), crashDetectionState = aCrashDetectionState().copy(crashDetected = false), ) @@ -37,8 +35,6 @@ open class RootStateProvider : PreviewParameterProvider { } fun aRootState() = RootState( - isShowkaseButtonVisible = false, rageshakeDetectionState = aRageshakeDetectionState(), crashDetectionState = aCrashDetectionState(), - eventSink = {} ) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/root/RootView.kt b/appnav/src/main/kotlin/io/element/android/appnav/root/RootView.kt index e5eb25704d..fc25ee65b1 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/root/RootView.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/root/RootView.kt @@ -37,7 +37,6 @@ fun RootView( state: RootState, modifier: Modifier = Modifier, onOpenBugReport: () -> Unit = {}, - onOpenShowkase: () -> Unit = {}, children: @Composable BoxScope.() -> Unit, ) { Box( @@ -46,7 +45,6 @@ fun RootView( contentAlignment = Alignment.TopCenter, ) { children() - val eventSink = state.eventSink fun onOpenBugReport() { state.crashDetectionState.eventSink(CrashDetectionEvents.ResetAppHasCrashed) @@ -54,11 +52,6 @@ fun RootView( onOpenBugReport.invoke() } - ShowkaseButton( - isVisible = state.isShowkaseButtonVisible, - onCloseClicked = { eventSink(RootEvents.HideShowkaseButton) }, - onClick = onOpenShowkase - ) RageshakeDetectionView( state = state.rageshakeDetectionState, onOpenBugReport = ::onOpenBugReport, diff --git a/appnav/src/main/kotlin/io/element/android/appnav/root/ShowkaseButton.kt b/appnav/src/main/kotlin/io/element/android/appnav/root/ShowkaseButton.kt deleted file mode 100644 index 23dbbd4397..0000000000 --- a/appnav/src/main/kotlin/io/element/android/appnav/root/ShowkaseButton.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2023 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.appnav.root - -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Close -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import io.element.android.libraries.designsystem.preview.ElementPreviewDark -import io.element.android.libraries.designsystem.preview.ElementPreviewLight -import io.element.android.libraries.designsystem.theme.components.Button -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 - -@Composable -internal fun ShowkaseButton( - isVisible: Boolean, - modifier: Modifier = Modifier, - onClick: () -> Unit = {}, - onCloseClicked: () -> Unit = {}, -) { - if (isVisible) { - Button( - modifier = modifier - .padding(top = 32.dp), - onClick = onClick - ) { - Text(text = "Showkase Browser") - IconButton( - modifier = Modifier - .padding(start = 8.dp) - .size(16.dp), - onClick = onCloseClicked, - ) { - Icon(imageVector = Icons.Filled.Close, contentDescription = "Close showkase button") - } - } - } -} - -@Preview -@Composable -internal fun ShowkaseButtonLightPreview() = ElementPreviewLight { ContentToPreview() } - -@Preview -@Composable -internal fun ShowkaseButtonDarkPreview() = ElementPreviewDark { ContentToPreview() } - -@Composable -private fun ContentToPreview() { - ShowkaseButton(isVisible = true) -} diff --git a/appnav/src/test/kotlin/io/element/android/appnav/RootPresenterTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/RootPresenterTest.kt index d0ad2adb2e..2938ce41df 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/RootPresenterTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/RootPresenterTest.kt @@ -22,7 +22,6 @@ import app.cash.molecule.RecompositionClock import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.appnav.root.RootEvents import io.element.android.appnav.root.RootPresenter import io.element.android.features.rageshake.impl.crash.DefaultCrashDetectionPresenter import io.element.android.features.rageshake.impl.detection.DefaultRageshakeDetectionPresenter @@ -44,21 +43,7 @@ class RootPresenterTest { }.test { skipItems(1) val initialState = awaitItem() - assertThat(initialState.isShowkaseButtonVisible).isTrue() - } - } - - @Test - fun `present - hide showkase button`() = runTest { - val presenter = createPresenter() - moleculeFlow(RecompositionClock.Immediate) { - presenter.present() - }.test { - skipItems(1) - val initialState = awaitItem() - assertThat(initialState.isShowkaseButtonVisible).isTrue() - initialState.eventSink.invoke(RootEvents.HideShowkaseButton) - assertThat(awaitItem().isShowkaseButtonVisible).isFalse() + assertThat(initialState.crashDetectionState.crashDetected).isFalse() } } diff --git a/build.gradle.kts b/build.gradle.kts index 2ce2922189..3d049e5376 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -285,12 +285,15 @@ tasks.register("runQualityChecks") { // Make sure to delete old screenshots before recording new ones subprojects { - val snapshotsDir = File("${project.path}/src/test/snapshots") + val snapshotsDir = File("${project.projectDir}/src/test/snapshots") val removeOldScreenshotsTask = tasks.register("removeOldSnapshots") { onlyIf { snapshotsDir.exists() } doFirst { + println("Delete previous screenshots located at $snapshotsDir\n") snapshotsDir.deleteRecursively() } } tasks.findByName("recordPaparazzi")?.dependsOn(removeOldScreenshotsTask) + tasks.findByName("recordPaparazziDebug")?.dependsOn(removeOldScreenshotsTask) + tasks.findByName("recordPaparazziRelease")?.dependsOn(removeOldScreenshotsTask) } diff --git a/docs/screenshot_testing.md b/docs/screenshot_testing.md index cf1973ab4e..37299af7fc 100644 --- a/docs/screenshot_testing.md +++ b/docs/screenshot_testing.md @@ -30,11 +30,16 @@ If installed correctly, `git push` and `git pull` will now include LFS content. ## Recording -It's recommended to delete the content of the folder `/snapshots` before recording. +```shell +./gradlew recordPaparazziDebug +``` + +The task will delete the content of the folder `/snapshots` before recording (see the task `removeOldSnapshots` defined in the project). + +If this is not the case, you can run ```shell rm -rf ./tests/uitests/src/test/snapshots -./gradlew recordPaparazziDebug ``` Paparazzi will generate images in `:tests:uitests/src/test/snapshots`, which will need to be committed to the repository using Git LFS. diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsNode.kt index 5b0795257c..7b89c7c227 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsNode.kt @@ -16,8 +16,11 @@ package io.element.android.features.preferences.impl.developer +import android.app.Activity import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import com.airbnb.android.showkase.ui.ShowkaseBrowserActivity import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin @@ -35,11 +38,21 @@ class DeveloperSettingsNode @AssistedInject constructor( @Composable override fun View(modifier: Modifier) { + val activity = LocalContext.current as Activity + fun openShowkase() { + val intent = ShowkaseBrowserActivity.getIntent( + context = activity, + rootModuleCanonicalName = "io.element.android.libraries.designsystem.showkase.DesignSystemShowkaseRootModule" + ) + activity.startActivity(intent) + } + val state = presenter.present() DeveloperSettingsView( state = state, modifier = modifier, - onBackPressed = this::navigateUp + onOpenShowkase = ::openShowkase, + onBackPressed = ::navigateUp ) } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt index 9b8c5e9cde..697081c397 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt @@ -14,28 +14,18 @@ * limitations under the License. */ -@file:OptIn(ExperimentalMaterial3Api::class) - package io.element.android.features.preferences.impl.developer -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.imePadding -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.statusBars -import androidx.compose.foundation.layout.systemBarsPadding -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter -import io.element.android.libraries.designsystem.components.preferences.PreferenceTopAppBar +import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory +import io.element.android.libraries.designsystem.components.preferences.PreferenceText +import io.element.android.libraries.designsystem.components.preferences.PreferenceView import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight -import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.featureflag.ui.FeatureListView import io.element.android.libraries.featureflag.ui.model.FeatureUiModel import io.element.android.libraries.ui.strings.R @@ -43,45 +33,42 @@ import io.element.android.libraries.ui.strings.R @Composable fun DeveloperSettingsView( state: DeveloperSettingsState, - modifier: Modifier = Modifier, + onOpenShowkase: () -> Unit, onBackPressed: () -> Unit, + modifier: Modifier = Modifier, ) { - Scaffold( - modifier = modifier - .fillMaxSize() - .systemBarsPadding() - .imePadding(), - contentWindowInsets = WindowInsets.statusBars, - topBar = { - PreferenceTopAppBar( - title = stringResource(id = R.string.common_developer_options), - onBackPressed = onBackPressed, + PreferenceView( + modifier = modifier, + onBackPressed = onBackPressed, + title = stringResource(id = R.string.common_developer_options) + ) { + // Note: this is OK to hardcode strings in this debug screen. + PreferenceCategory(title = "Feature flags") { + FeatureListContent(state) + } + PreferenceCategory(title = "Showkase") { + PreferenceText( + title = "Open Showkase browser", + onClick = onOpenShowkase ) - }, - content = { - FeatureListContent(it, state) } - ) + } } @Composable fun FeatureListContent( - paddingValues: PaddingValues, state: DeveloperSettingsState, modifier: Modifier = Modifier ) { - fun onFeatureEnabled(feature: FeatureUiModel, isEnabled: Boolean) { state.eventSink(DeveloperSettingsEvents.UpdateEnabledFeature(feature, isEnabled)) } - Box( - modifier = modifier - .padding(paddingValues) - .fillMaxSize() - ) { - FeatureListView(features = state.features, onCheckedChange = ::onFeatureEnabled) - } + FeatureListView( + modifier = modifier, + features = state.features, + onCheckedChange = ::onFeatureEnabled, + ) } @Preview @@ -98,6 +85,7 @@ fun DeveloperSettingsViewDarkPreview(@PreviewParameter(DeveloperSettingsStatePro private fun ContentToPreview(state: DeveloperSettingsState) { DeveloperSettingsView( state = state, + onOpenShowkase = {}, onBackPressed = {} ) } diff --git a/libraries/featureflag/ui/src/main/kotlin/io/element/android/libraries/featureflag/ui/FeatureListView.kt b/libraries/featureflag/ui/src/main/kotlin/io/element/android/libraries/featureflag/ui/FeatureListView.kt index 280e062110..2f17482875 100644 --- a/libraries/featureflag/ui/src/main/kotlin/io/element/android/libraries/featureflag/ui/FeatureListView.kt +++ b/libraries/featureflag/ui/src/main/kotlin/io/element/android/libraries/featureflag/ui/FeatureListView.kt @@ -16,8 +16,7 @@ package io.element.android.libraries.featureflag.ui -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview @@ -34,14 +33,10 @@ fun FeatureListView( onCheckedChange: (FeatureUiModel, Boolean) -> Unit, modifier: Modifier = Modifier ) { - LazyColumn( + Column( modifier = modifier, ) { - items( - items = features, - key = { it.key } - ) { feature -> - + features.forEach { feature -> fun onCheckedChange(isChecked: Boolean) { onCheckedChange(feature, isChecked) } diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png index 0298cc02c7..57b4a846e2 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d07bdb794fe858dfc3e3122c10b24dcf5a4918d21deb3c87686139a2d94f4f43 -size 18918 +oid sha256:5d806cbab0f26fb4f471adb5fbecfc600603a7651c5391501d42b13b23617a4c +size 29301 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewLightPreview_0_null_0,NEXUS_5,1.0,en].png index 5ae68d249f..d8d2dbc8d1 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e944e15b9eb9477e57b002d6b16c8a28bc8bc25d8bef40cdf6982509e4424c7f -size 18211 +oid sha256:d04eb5d2ebb8b740edcaac00912994e8c164e7b5083595d4085d350af0ca6c33 +size 28482