From ce20a01ac0b64da9433564194eb46386ee618d56 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 25 Aug 2025 14:44:54 +0200 Subject: [PATCH] Add a way to customize the on boarding logo. --- .../onboarding/CustomLogoResIdProvider.kt | 32 +++++++++++++++++++ .../screens/onboarding/OnBoardingPresenter.kt | 5 +++ .../screens/onboarding/OnBoardingState.kt | 3 ++ .../onboarding/OnBoardingStateProvider.kt | 6 ++++ .../impl/screens/onboarding/OnBoardingView.kt | 30 ++++++++++++++++- features/login/impl/src/main/res/raw/keep.xml | 12 +++++++ .../onboarding/OnBoardingPresenterTest.kt | 14 ++++++++ .../atomic/pages/OnBoardingPage.kt | 17 ++++++---- 8 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/CustomLogoResIdProvider.kt create mode 100644 features/login/impl/src/main/res/raw/keep.xml diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/CustomLogoResIdProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/CustomLogoResIdProvider.kt new file mode 100644 index 0000000000..382ec66490 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/CustomLogoResIdProvider.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.login.impl.screens.onboarding + +import android.annotation.SuppressLint +import android.content.Context +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.ApplicationContext +import javax.inject.Inject + +fun interface CustomLogoResIdProvider { + fun get(): Int? +} + +@ContributesBinding(AppScope::class) +class DefaultCustomLogoResIdProvider @Inject constructor( + @ApplicationContext private val context: Context, +) : CustomLogoResIdProvider { + @SuppressLint("DiscouragedApi") + override fun get(): Int? { + val resId = context.resources + .getIdentifier("custom_logo", "drawable", context.packageName) + .takeIf { it != 0 } + return resId + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt index 0e545f44e6..500affeac5 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt @@ -36,6 +36,7 @@ class OnBoardingPresenter @AssistedInject constructor( private val defaultAccountProviderAccessControl: DefaultAccountProviderAccessControl, private val rageshakeFeatureAvailability: RageshakeFeatureAvailability, private val loginHelper: LoginHelper, + private val customLogoResIdProvider: CustomLogoResIdProvider, ) : Presenter { @AssistedFactory interface Factory { @@ -81,6 +82,9 @@ class OnBoardingPresenter @AssistedInject constructor( } val canReportBug by remember { rageshakeFeatureAvailability.isAvailable() }.collectAsState(false) var showReportBug by rememberSaveable { mutableStateOf(false) } + val customLogoResId = remember { + customLogoResIdProvider.get() + } val loginMode by loginHelper.collectLoginMode() @@ -112,6 +116,7 @@ class OnBoardingPresenter @AssistedInject constructor( canReportBug = canReportBug && showReportBug, loginMode = loginMode, version = buildMeta.versionName, + customLogoResId = customLogoResId, eventSink = ::handleEvent, ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingState.kt index 1e55c3af2d..8d29544483 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingState.kt @@ -7,6 +7,7 @@ package io.element.android.features.login.impl.screens.onboarding +import androidx.annotation.DrawableRes import io.element.android.features.login.impl.login.LoginMode import io.element.android.libraries.architecture.AsyncData @@ -18,6 +19,8 @@ data class OnBoardingState( val canCreateAccount: Boolean, val canReportBug: Boolean, val version: String, + @DrawableRes + val customLogoResId: Int?, val loginMode: AsyncData, val eventSink: (OnBoardingEvents) -> Unit, ) { diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingStateProvider.kt index cdf77da523..ce32923a99 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingStateProvider.kt @@ -7,9 +7,11 @@ package io.element.android.features.login.impl.screens.onboarding +import androidx.annotation.DrawableRes import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.login.impl.login.LoginMode import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.designsystem.R open class OnBoardingStateProvider : PreviewParameterProvider { override val values: Sequence @@ -20,6 +22,7 @@ open class OnBoardingStateProvider : PreviewParameterProvider { anOnBoardingState(canLoginWithQrCode = true, canCreateAccount = true), anOnBoardingState(canLoginWithQrCode = true, canCreateAccount = true, canReportBug = true), anOnBoardingState(defaultAccountProvider = "element.io", canCreateAccount = false, canReportBug = true), + anOnBoardingState(customLogoResId = R.drawable.sample_background), ) } @@ -31,6 +34,8 @@ fun anOnBoardingState( canCreateAccount: Boolean = false, canReportBug: Boolean = false, version: String = "1.0.0", + @DrawableRes + customLogoResId: Int? = null, loginMode: AsyncData = AsyncData.Uninitialized, eventSink: (OnBoardingEvents) -> Unit = {}, ) = OnBoardingState( @@ -42,5 +47,6 @@ fun anOnBoardingState( canReportBug = canReportBug, version = version, loginMode = loginMode, + customLogoResId = customLogoResId, eventSink = eventSink, ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingView.kt index 6c67e75cd1..a8428dd6af 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingView.kt @@ -7,6 +7,7 @@ package io.element.android.features.login.impl.screens.onboarding +import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -19,9 +20,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.BiasAlignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewParameter @@ -67,8 +70,15 @@ fun OnBoardingView( ) { OnBoardingPage( modifier = modifier, + renderBackground = state.customLogoResId == null, content = { - OnBoardingContent(state = state) + if (state.customLogoResId != null) { + OnBoardingSimpleLogo( + customLogoResId = state.customLogoResId, + ) + } else { + OnBoardingContent(state = state) + } LoginModeView( loginMode = state.loginMode, onClearError = { @@ -139,6 +149,24 @@ private fun OnBoardingContent(state: OnBoardingState) { } } +@Composable +private fun OnBoardingSimpleLogo( + customLogoResId: Int, + modifier: Modifier = Modifier, +) { + Box( + modifier = modifier + .fillMaxSize() + .padding(16.dp), + contentAlignment = Alignment.Center, + ) { + Image( + painter = painterResource(id = customLogoResId), + contentDescription = null + ) + } +} + @Composable private fun OnBoardingButtons( state: OnBoardingState, diff --git a/features/login/impl/src/main/res/raw/keep.xml b/features/login/impl/src/main/res/raw/keep.xml new file mode 100644 index 0000000000..d7a93dd9b1 --- /dev/null +++ b/features/login/impl/src/main/res/raw/keep.xml @@ -0,0 +1,12 @@ + + + diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt index b47adf4cd8..34aaef269d 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt @@ -84,6 +84,18 @@ class OnBoardingPresenterTest { } } + @Test + fun `present - custom logo`() = runTest { + val presenter = createPresenter( + customLogoResIdProvider = CustomLogoResIdProvider { 42 }, + ) + presenter.test { + skipItems(1) + val initialState = awaitItem() + assertThat(initialState.customLogoResId).isEqualTo(42) + } + } + @Test fun `present - clicking on version 7 times has no effect if rageshake not available`() = runTest { val presenter = createPresenter( @@ -224,6 +236,7 @@ private fun createPresenter( wellknownRetriever: WellknownRetriever = FakeWellknownRetriever(), rageshakeFeatureAvailability: () -> Flow = { flowOf(true) }, loginHelper: LoginHelper = createLoginHelper(), + customLogoResIdProvider: CustomLogoResIdProvider = CustomLogoResIdProvider { null }, ) = OnBoardingPresenter( params = params, buildMeta = buildMeta, @@ -234,6 +247,7 @@ private fun createPresenter( ), rageshakeFeatureAvailability = rageshakeFeatureAvailability, loginHelper = loginHelper, + customLogoResIdProvider = customLogoResIdProvider, ) fun createLoginHelper( diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/OnBoardingPage.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/OnBoardingPage.kt index 716d4a46ce..a5de10ada3 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/OnBoardingPage.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/OnBoardingPage.kt @@ -38,6 +38,7 @@ import io.element.android.libraries.designsystem.theme.components.Text @Composable fun OnBoardingPage( modifier: Modifier = Modifier, + renderBackground: Boolean = true, contentAlignment: Alignment.Horizontal = Alignment.CenterHorizontally, footer: @Composable () -> Unit = {}, content: @Composable () -> Unit = {}, @@ -47,13 +48,15 @@ fun OnBoardingPage( .fillMaxSize() ) { // BG - Image( - modifier = Modifier - .fillMaxSize(), - painter = painterResource(id = R.drawable.onboarding_bg), - contentScale = ContentScale.Crop, - contentDescription = null, - ) + if (renderBackground) { + Image( + modifier = Modifier + .fillMaxSize(), + painter = painterResource(id = R.drawable.onboarding_bg), + contentScale = ContentScale.Crop, + contentDescription = null, + ) + } Column( modifier = Modifier .fillMaxSize()